Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix merge conflicts - #317

Closed
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix
Closed

Fix merge conflicts#317
OisinKyne wants to merge 49 commits into
docs/spec-bundlefrom
oisin/spec-bundle-fix

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

No description provided.

bussyjdand others added 30 commits March 23, 2026 11:04
Add monetize-guide skill (SKILL.md + seller-prompt reference) that teaches
Claude Code the end-to-end flow: pre-flight checks, model detection,
pricing research via ERC-8004 registry, user-confirmed pricing, sell
execution, reconciliation monitoring, and endpoint verification.
Add `obol sell probe <name> -n <ns>` command that hits the public tunnel
URL and verifies the endpoint returns 402 with valid x402 pricing headers.
Closes the feedback loop so Claude can confirm a service is live.
--per-hour was passing the raw hourly price as the per-request charge
(e.g., $0.50/hour charged $0.50 per HTTP request). Now approximates
using a 5-minute experiment budget: perRequest = perHour * 5/60.
Also rewrites worker_api.py to use stdlib http.server (no Flask dep).
…erage
Critical:
- Fix path traversal in worker_api.py: validate experiment_id with regex
- Add GGUF format guard in publish.py before ollama_create()
- Run worker container as non-root user in Dockerfile
Medium:
- Replace manual provenance struct-to-map with JSON round-trip in sell.go
- Fix weak test assertion in TestApproximateRequestPriceFromPerHour
- Guard int(amount) cast in coordinate.py with try/except
- Remove domain-specific default "val_bpb" from CRD metricName field
- Guard maxTimeoutSeconds parse in monetize.py
Low:
- Add provenance propagation test for build_registration_doc
- Fix doc type inconsistency in coordination-protocol.md
- Assert all 6 Provenance fields in store_test.go
# Conflicts:
#	cmd/obol/sell.go
#	internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
#	internal/embed/skills/autoresearch-coordinator/references/coordination-protocol.md
#	internal/embed/skills/autoresearch-coordinator/scripts/coordinate.py
#	internal/embed/skills/autoresearch/SKILL.md
#	internal/embed/skills/autoresearch/scripts/publish.py
#	internal/embed/skills/sell/scripts/monetize.py
#	internal/inference/store_test.go
#	tests/test_sell_registration_metadata.py
- Add `obol sell probe <name> -n <ns>` — sends unauthenticated request
through Traefik to verify the endpoint returns 402 with x402 pricing.
- Create flow-06-sell-setup.sh: idempotent ServiceOffer creation
- Create flow-07-sell-verify.sh: wait for reconciliation + verify conditions
- Create flow-10-anvil-facilitator.sh: Anvil fork + x402-rs facilitator
- Create flow-08-buy.sh: EIP-712 sign + paid request through Traefik
All flows pass end-to-end: sell → verify → pay → inference (HTTP 200).
- Add skills and domains array fields to ServiceOffer CRD registration
schema. The --register-skills CLI flag was rejected by strict CRD
validation because the fields were missing.
- Fix coordinator.py parse_402_pricing to handle x402 V1 standard
format (accepts[] array) in addition to flat top-level fields.
Validated: worker ServiceOffer → 402 gate → .well-known → discovery →
coordinator probe.
Paths validated against https://github.com/agntcy/oasf (cloned at R&D/oasf/).
Skills:
- natural_language_processing/text_generation/chat_completion → natural_language_processing/natural_language_generation/text_completion
- machine_learning/model_optimization → analytical_skills/model_optimization
Domains:
- technology/artificial_intelligence → technology/data_science
- technology/artificial_intelligence/research → research_and_development/scientific_research
Fixed in: monetize.py, coordinate.py, monetize-guide SKILL.md, autoresearch SKILL.md,
autoresearch-worker SKILL.md, coordinator references, seller-prompt.md
analytical_skills/model_optimization was invented (doesn't exist in OASF).
Closest real path: devops_mlops/model_versioning (validated against
agntcy/oasf schema repo at R&D/oasf/).
Also confirms prior fixes:
- natural_language_processing/natural_language_generation/text_completion
- technology/data_science
- research_and_development/scientific_research
LiteLLM does NOT auto-append /v1 for openai/ provider routes.
WarnAndStripV1Suffix was removing /v1 from api_base before storing
in the ConfigMap, causing LiteLLM to hit /chat/completions (404)
instead of /v1/chat/completions.
Verified against LiteLLM source (gpt_transformation.py:get_complete_url):
api_base is passed as-is to the OpenAI SDK base_url, which appends
/chat/completions directly. The /v1 must be in api_base.
Removed from all 3 call sites:
- cmd/obol/model.go (obol model setup custom)
- internal/openclaw/openclaw.go (promptForDirectProvider)
- internal/openclaw/openclaw.go (promptForCustomProvider)
Adds a new "External LAN Resources" section to the monetize-guide SKILL.md
covering the flow for selling GPU servers or inference endpoints on the
local network (e.g., DGX Spark running vLLM).
The path: obol model setup custom (bridge into LiteLLM) → obol sell http
(create ServiceOffer pointing at LiteLLM). Documents that LAN IPs are
reachable from k3d without additional config, and that --endpoint must
include /v1 since LiteLLM does not auto-append it.
Validated end-to-end with Nemotron 3 Super 120B on 2x DGX Spark:
- obol model setup custom validates and adds to LiteLLM
- obol sell http creates ServiceOffer
- Agent heartbeat reconciles in ~90s → all 6 conditions True
- 402 gating works locally and through Cloudflare tunnel
- .well-known and /skill.md discovery updated automatically
…cherry-pick
Cherry-picking PR #265 into feat/monetize-path had conflicts in sell.go
that dropped 3 blocks of code from main:
1. obol sell inference — cluster-aware routing: detects k3d cluster,
creates K8s Service+Endpoints bridge to host gateway, creates
ServiceOffer, auto-starts tunnel via EnsureTunnelForSell()
2. obol sell http — auto-tunnel: calls EnsureTunnelForSell() after
creating a ServiceOffer so the endpoint is immediately public
3. obol sell delete — auto-stop tunnel: when the last ServiceOffer is
deleted, stops the quick tunnel and removes the storefront
Also restores:
- NoPaymentGate field on Deployment and GatewayConfig structs
- createHostService(), resolveHostIP(), buildInferenceServiceOfferSpec()
- net, runtime, strconv, stack imports
LiteLLM PyPI packages 1.82.7 and 1.82.8 contain a malicious .pth file
(litellm_init.pth) that exfiltrates environment variables, SSH keys,
cloud credentials, and Kubernetes configs to an external endpoint.
See: BerriAI/litellm#24512
Our template used the floating tag `main-stable` which could pull a
compromised build. Pin to `main-v1.82.3` (confirmed safe, matches
the version currently running in our clusters).
Never use floating tags for security-sensitive dependencies.
The cherry-pick of PR #265 dropped _build_skill_md() and
_publish_skill_md() from monetize.py, along with their 3 call sites
in cmd_process. This meant /skill.md would never be created or updated
on a fresh cluster.
Restores:
- _build_skill_md(): generates service catalog markdown from Ready offers
- _publish_skill_md(): creates/updates ConfigMap + Deployment + Service +
HTTPRoute for the /skill.md endpoint
- 3 call sites in cmd_process:
1. Empty skill.md when no offers exist
2. Full skill.md when all offers are Ready
3. Regenerate after reconciliation loop
No tests existed for tunnel state persistence or auto-stop decision
logic — this is why the cherry-pick drift went undetected.
New tests in tunnel_lifecycle_test.go:
- State round-trip (save/load, quick & dns modes)
- Missing state file returns (nil, nil)
- State overwrite replaces previous
- File permissions (0600 for non-secret metadata)
- UpdatedAt timestamp refresh on save
- tunnelModeAndURL derivation
- shouldAutoStopTunnel decision logic (5 cases covering the logic
from sell delete: stop quick tunnels when empty, never stop dns)
- Exported LoadTunnelState wrapper
…ures
Two issue specifications for carving PR #288 into focused PRs:
1. reth-erc8004-indexer Helm chart
- Standalone chart with 3-tier discovery fallback
- Reth indexer → BaseScan (native ERC-8004 metadata) → 8004scan
- Go DiscoveryClient interface with FallbackClient
2. Autoresearch infrastructure Helm chart
- Round-based reward engine with OPOW influence calculation
- Anti-monopoly parity formula penalizing concentrated workers
- Commit-reveal Merkle proof verification
- Escrow settlement via x402 Commerce Payments Protocol
(5x-audited Base contracts, zero custom Solidity)
- x402-rs implications and contribution path
9 Gherkin feature files (40+ scenarios):
- escrow_round_lifecycle.feature (fixed: receiver is per-PaymentInfo,
uses RewardDistributor contract as single receiver)
- opow_influence_calculation_with_anti_monopoly_parity.feature
(fixed: penalty values verified against TIG opow.rs math,
added single-challenge and phase-in scenarios)
- commit_reveal_work_verification.feature
- reward_pool_distribution_across_roles.feature
- multi_tier_worker_discovery_with_fallback.feature
- end_to_end_autoresearch_round.feature
- erc8004_identity_lifecycle.feature (NEW: registration, transfer,
deactivation, schema validation during active rounds)
- leaderboard_api.feature (NEW: REST API, historical rounds,
cumulative earnings)
- round_state_continuity.feature (NEW: fund rollover, atomic
transitions, worker state reset, on-chain auditability)
Cross-reference analysis:
- escrow_contract_cross_reference.md (AuthCaptureEscrow contract audit)
- FEATURE_REVIEW.md (comprehensive gap analysis vs ERC-8004/x402)
Platform wallet is payer, operator, AND receiver. Escrow is used for
verifiable commitment (workers see locked pool on-chain before working),
not for routing. After capture() returns USDC to platform wallet,
standard ERC20 transfers distribute to workers/innovators.
Zero custom Solidity. Zero new deployments. Zero new audits.
Only AuthCaptureEscrow (5x audited) + USDC.transfer().
Install backend-service-spec-bundler skill and generate comprehensive
spec bundle covering all 9 core subsystems: stack lifecycle, LLM routing,
network/RPC gateway, sell-side monetization, buy-side payments, OpenClaw
& skills, tunnel management, ERC-8004 identity, standalone inference.
- SPEC.md: 1452-line technical specification with Mermaid diagrams
- ARCHITECTURE.md: C4 diagrams, module decomposition, data flows
- BEHAVIORS_AND_EXPECTATIONS.md: 28 desired, 6 undesired, 7 edge cases
- CONTRIBUTING.md: 9 non-negotiable developer rules
- 7 BDD feature files (126 Gherkin scenarios)
- 6 ADRs (k3d, LiteLLM, x402, pre-signed buyer, Gateway API, ERC-8004)
* chore: update CLAUDE.md conventions and fix stale justfile commands
- Add Conventions section (conventional commits, branch prefixes, skill reference)
- Trim discoverable sections (Key Packages table, file-path tables, Embedded assets)
- Keep full LLM Routing, Security, Pitfalls sections
- Fix justfile: obol cluster → obol stack (stale command name)
* chore: add golangci-lint v2 config and PostToolUse format hook
Aligned with ObolNetwork/charon lint stance, adapted for obol-stack:
- Disabled linters not applicable to CLI apps (wrapcheck, forbidigo, noctx, testpackage)
- Enabled gofumpt + goimports formatters
- Added PostToolUse hook to auto-lint Go files on Write|Edit
Many linters are disabled with TODO comments for incremental re-enablement.
* style: apply gofumpt and goimports formatting across codebase
Auto-fix from golangci-lint v2 with gofumpt + goimports formatters.
Also removes unused code: sellInfoCommand (cmd/obol/sell.go),
pubKeyBytes (internal/tee/attest_stub.go), and fixes dogsled/thelper/govet
warnings.
* security: re-enable gosec linter and fix all issues
- Tighten WriteFile permissions from 0644 to 0600 across 13 files
(config, keys, kubeconfig — no reason for group/world read on local CLI)
- Exclude G204 (subprocess) and G101 (credential naming) — systemic
false positives for a CLI tool that intentionally runs k3d/helm/kubectl
- Exclude gosec from internal/testutil/ (test-only helpers)
- Nolint G704 (SSRF), G705 (XSS), G122 (TOCTOU), G703 (path traversal)
with explanations — all false positives for local config operations
* chore: re-enable 10 linters and fix all issues
Re-enabled: errcheck, errchkjson, gocritic, nilerr, nilnil,
nosprintfhostport, staticcheck, unconvert, wastedassign.
Fixes:
- errcheck: add _ = for best-effort calls (browser open, process kill,
kubectl display, io.Copy to discard)
- nilerr: annotate intentional fallback patterns (missing config files,
optional features, backward compat defaults)
- staticcheck: lowercase error strings (ST1005), rename hasId→hasID
(ST1003), nolint deprecated elliptic.Marshal (SA1019)
- gocritic: fix appendAssign slice mutation bugs (copy before append),
nolint CGo dupSubExpr false positives, exitAfterDefer in main
- unconvert: remove 5 unnecessary int() wrappers on cmd.Int()
- nosprintfhostport: use net.JoinHostPort in testutil
- wastedassign: use var declaration instead of empty init
Still disabled: goconst (32), unparam (28) — low priority.
* chore: re-enable goconst and unparam — all linters now active
- Extract provider name constants (ProviderOllama, ProviderAnthropic,
ProviderOpenAI) into internal/model and use across model/openclaw
- Extract API key env var constants (envAnthropicAPIKey, envOpenAIAPIKey)
- Extract OS name constants in dns/resolver, update status messages in
update package, version unknownValue constant
- Add isDevMode() helper in config to eliminate repeated "true" comparison
- Simplify buildLocalManagedSecretYAML: remove always-nil error return
and unused hostname parameter
- Exclude goconst and unparam from test files (test fixtures, mock
handler signatures)
- Nolint validateInstallOptions networkName — extensibility param
All linters from .golangci.yml are now active with zero issues.
* fix: address PR review feedback on lint config and hook
- Set fix: false in .golangci.yml — lint runs are now non-mutating by
default, suitable for CI. Use --fix flag explicitly when wanted.
- Hook: use golangci-lint via PATH instead of hardcoded ~/.local/bin/
- Hook: use `golangci-lint fmt "$f"` to scope formatting to the edited
file only, avoiding unrelated rewrites across the package
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
…netize-path
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/tunnel/tunnel.go
docs: replace legacy spec bundle with PR288-based canonical bundle
bussyjdand others added 19 commits March 30, 2026 06:43
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up
.workspace/data/ directories that contain root-owned PVC mounts from
previous clusters, causing "permission denied" errors during context
scanning.
Exclude .workspace/ and .worktrees/ from the Docker build context via
.dockerignore.
Fixes#304
fix: exclude .workspace from Docker build context (#304)
- Add balance guard to buy.py — exits on insufficient USDC unless --force,
tells the agent which wallet to fund
- Add end-to-end "Full Buy Flow" section to buy-inference SKILL.md covering
discover → probe → buy → use paid/<model> → monitor/refill
- Add "After Discovery" cross-reference from discovery SKILL.md to buy-inference
- Add Docker image fallback for openclaw CLI install in obolup.sh for
npm-deprived environments (ARM64 DGX Spark validated)
Validated full buyer→seller x402 commerce loop on DGX Spark (Base Sepolia):
seller registers on ERC-8004, obol-agent discovers via registry, probes 402,
pre-signs ERC-3009 auths, paid/qwen3.5:9b routes through x402-buyer sidecar.
Previously, stageDefaultSkills skipped entirely if the skills/ directory
already existed. This meant skills added to the binary after initial
deployment (e.g. buy-inference, discovery) were never staged on re-sync,
requiring a manual `obol openclaw skills sync --from` workaround.
Now always runs CopySkills, which writes embedded files without deleting
user-added content. Validated on DGX Spark (ARM64): obol openclaw sync
correctly re-stages all 21 skills into an existing deployment, and the
full x402 commerce loop (probe → buy 5 auths → 5/5 paid inference →
exact depletion on 6th request) passes.
- Rename `copy` → `patched` in controller.go to avoid shadowing the
built-in copy function (4 occurrences)
- Revert cosmetic whitespace changes in kubectl.go and verifier.go that
added review noise without functional benefit
- Add ApplyOutput to kubectl.go cleanly atop the original formatting
- Remove SetRegistration/HandleWellKnown from verifier (now handled by
serviceoffer-controller registration resources)
- Update CLAUDE.md dev constraints to mention serviceoffer-controller image
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
- CLAUDE.md: take main's Key Packages table, add new controller entries
- dns/resolver.go: take main's quiet error style (no verbose print)
https://claude.ai/code/session_016ktZi3zLxbnWrjF8cLyTjm
…ec-bundle
# Conflicts:
#	docs/monetisation-architecture-proposal.md
#	docs/x402-test-plan.md
#	plans/monetise.md
The spec bundle still described the old monetize.py/heartbeat-driven
reconciliation architecture. Update all canonical docs to reflect the
actual code on feat/monetize-path:
- Replace monetize.py references with serviceoffer-controller
- Document RegistrationRequest CRD and finalizer-based cleanup
- Update agent.Init() from RBAC patching to legacy heartbeat removal
- Add sell info, sell stop (pause annotation), sell delete (tombstoning)
- Update RBAC model: controller has own SA, agent gets minimal write
- Add spec.provenance, registration.skills/domains/metadata fields
- Update all Mermaid diagrams (C4, sequence, deployment)
Closes#297https://claude.ai/code/session_014xHg1n4FxvjPgXgkJFMQrT
Three fixes for cross-machine paid inference via facilitator.x402.rs:
1. x402-verifier CA certificates (x402.yaml):
The verifier container image lacks a system CA bundle, causing TLS
failures when connecting to facilitator.x402.rs:
x509: certificate signed by unknown authority
Added a ca-certificates ConfigMap (populated at deploy time from the
host cert store) mounted at /etc/ssl/certs in the verifier pod.
2. Consumer key: Hardhat #9 -> Hardhat #0 (lib.sh, flow-08-buy.sh):
Base Sepolia USDC (0x036CbD53) rejects ERC-3009
transferWithAuthorization signatures from Hardhat accounts #1-#9
(FiatTokenV2: invalid signature). Only HH#0 and custom keys pass
validation. Root cause unclear but consistently reproducible.
Tested: Real on-chain USDC settlement on Base Sepolia confirmed.
TX: 0xa95cc751a3cd010acde7814719db0f539a53829849c4aba05ebb624009e20a4f
Seller +0.001 USDC, Buyer -0.001 USDC.
- Replace all hardcoded Hardhat private keys with runtime derivation
from the well-known test mnemonic via cast wallet derive-private-key
- flow-08-buy.sh reads CONSUMER_PRIVATE_KEY from env (set by lib.sh)
- flow-10 derives FACILITATOR_SIGNER_KEY via hh_key() helper
- Add gitleaks pre-commit hook to block accidental secret commits
- Add .gitleaks.toml allowlist for the public test mnemonic
Zero private key material in source. Keys are derived at runtime
from: test test test test test test test test test test test junk
# Conflicts:
#	cmd/obol/model.go
#	cmd/obol/network.go
#	cmd/obol/sell.go
#	internal/openclaw/openclaw.go
#	internal/openclaw/wallet.go
#	internal/openclaw/wallet_test.go
@bussyjd

Copy link
Copy Markdown
Contributor

This is no longer needed — the docs/spec-bundle branch was rebased clean onto main (40 files, docs-only, no code baggage). PR #297 is now mergeable. Safe to close.

@bussyjd

Copy link
Copy Markdown
Contributor

Closing this — the docs/spec-bundle branch was rebased clean (docs only, 40 files).

However, your commit 3a1f043 has real code changes that should land separately:

  1. safeName() in render.go — truncate + hash K8s resource names exceeding 253 chars. Real bug fix.
  2. Controller logging — ERC-8004 key status at startup, metadata sync success/failure messages.
  3. reconcileDeletingOffer — return error (requeue) instead of silent nil when registration cleanup pending.
  4. Dead code removalAddRoute, WritePricingConfig, DeleteStaticOfferRoute and related RouteOption types from x402/setup.go.

Want me to cherry-pick these into a standalone PR against main?

@bussyjd

Copy link
Copy Markdown
Contributor

All code changes from this PR (safeName, controller logging, dead code removal) already landed on main via #316. Nothing left to extract — closing.

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the oisin/spec-bundle-fix branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@OisinKyne@bussyjd@claude