Skip to content

C-04: hide completion and fault rates on testnet (no-rate restriction) - #96

Draft
randomblocker wants to merge 30 commits into
agent/sdk-listing-alignmentfrom
stack/c04/w-no-rates
Draft

randomblocker wants to merge 30 commits into
agent/sdk-listing-alignmentfrom
stack/c04/w-no-rates

Conversation

@randomblocker

@randomblocker randomblocker commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #85 at 25f4c58, with C-01 integrated from main. Head: de70e1c.

Directory users see “Completion verification unavailable” and labelled observations. New and cached projections publish null metrics with a reason and TP-1 metadata, remove unknown cached rates, ratings and scores at seller, deal and listing level, and supply listing hints with observed category counts and snapshot windows in the shape the pinned SDK catalog reader accepts. Completion/rating filters, including bracketed keys, and sorting/ranking requests return 400. Evidence inspection, counts and currency totals remain available with their limitations stated. Full metrics return under a later reputation contract.

TP-1 is defined in-tree in reference-implementations/dacs-directory/docs/TP-1.md, measured against DACS Standard d45c0a29. It declares the Directory's open nonconforming deviations in the Standard's ImplementationDeviation shape. The manifest reports dacsDirectoryVersion: "2" and restrictions: ["TP-1"], the OpenAPI document is 2.0.0, and the ListingSummary schema is served with a short, revalidated cache lifetime.

Observed counts follow DACS-4 SB-1/SB-2 settlement uniqueness:

  • Each success-evidence paymentTxRefs entry is projected to its SB-1 settlement-tx-id (demos:{txHash}, including the legacy { rail: "demos", txHash } ref; evm:{chainId}:{txHash}:{logIndex} for evm-event and x402-event; solana:{cluster}:{signature}:{instructionIndex}). A deal citing a ref that can't be projected without ledger data isn't counted.
  • The complete admissible set is grouped by that id before windowing. A group claimed by more than one record or (jobId, phaseIndex) is void, because the Directory verifies no settlement-side binding authority. Seller reputation, indexed listing hints and served hints share this rule, and a served hint's count is recomputed from retained records.
  • The SB-1 ledger-event check and the anchor-address phaseIndex recovery aren't implemented. Both are declared as open deviations in docs/TP-1.md.

This combined head also includes two build changes beyond the W restriction:

Because main is merged here, the diff against #85 also contains main's #84/C-01 removal until #85 is updated from main. C-01 is included in this head and remains a merge prerequisite.

Validation at de70e1c: the 36 unit/component files that reach the changed modules pass 309/309 (node --import tsx --test --test-concurrency=2), and typecheck reports 0 errors. The previous head passed all 47 files (351 tests) and next build. Six retained browser cases were not run; no lint command is configured. Open for the owner: sign-off on deferring the SB-1 phaseIndex derivation and the ledger-event check.

🤖 Generated with Claude Code

random block and others added 30 commits August 10, 2026 17:21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The production-origin check accepted https://localhost, loopback and
unspecified IP literals, so a mistyped NEXT_PUBLIC_DIRECTORY_URL could pass
the deploy gate and poison canonical URLs, the sitemap and llms.txt.

- Reject localhost, *.localhost, *.local, and loopback, unspecified,
  private, CGNAT and link-local IPv4/IPv6 literals (including
  IPv4-mapped IPv6).
- Require the raw variable to be exactly the origin (optionally with one
  trailing slash), because directoryBaseUrl() uses it verbatim; values
  like " https://x ", "https://x?" or "https://X" no longer pass.
- Assert the specific error for each rejection, add the password-only
  credential case, and move CI's directory URL off .local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-config

The deploy-config check rejected loopback, private, CGNAT and link-local
literals but still passed other addresses that cannot be a deployment's
public origin, such as https://192.0.2.1, https://224.0.0.1,
https://[2001:db8::1], https://[fec0::1] or https://[64:ff9b::7f00:1].
It also passed single-label and private-use names (https://directory,
https://directory.railway.internal) and names with empty labels
(https://localhost..).

- IPv4 literals: also reject the remaining RFC 6890 special-use ranges
  that are not globally reachable (192.0.0.0/24, 192.0.2.0/24,
  192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24), plus
  multicast, reserved and broadcast (224.0.0.0/4, 240.0.0.0/4).
- IPv6 literals: accept only global unicast (2000::/3) outside the
  IETF-protocol (2001::/23, including Teredo), documentation
  (2001:db8::/32, 3fff::/20) and 6to4 (2002::/16) blocks. IPv4-mapped
  addresses still follow the IPv4 rules; NAT64, IPv4-compatible and
  site-local forms are now rejected.
- Host names: require at least two non-empty labels (one trailing dot is
  still allowed) and also reject .internal, .home.arpa and .localdomain.
- Tests pin both edges of every blocked range, so each range can neither
  shrink nor grow, and cover the special-use, transition-form and name
  cases. The README states the full contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/codex-local-workflow.md arrived from main (#88) after this branch
retired the gateway demo, so it still named that demo's variables and
told contributors to run

  NEXT_PUBLIC_DIRECTORY_URL=https://directory.ci.local \
    NEXT_PUBLIC_BUTLER_ORIGIN=https://agents.ci.local \
    npm run check:deploy-config

check:deploy-config now rejects .local hosts, so that command exits 1.

- Pass only NEXT_PUBLIC_DIRECTORY_URL=https://directory.ci.example, the
  value CI sets, to check:deploy-config and build, and say why a local
  origin must not be substituted.
- Drop `unset RUN_LIVE_PAID_E2E LIVE_BUTLER_ORIGIN` and the "live/paid
  browser tests" wording; that suite no longer exists.
- Add a test that reads the CI workflow and the guide: CI's origin must
  pass the check, every NEXT_PUBLIC_* value the guide passes must equal
  CI's, and the guide must not name the retired variables. CI does not
  build docs, so this is what keeps the guide runnable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
https://directory.example:0 passed as the production origin, but no
browser can connect to port 0, so every canonical URL, sitemap entry and
discovery document built from it would be unusable. URL parsing also
turns :00 into :0, so the check reads the parsed port.

The README now lists port 0 among the rejected forms, and it names the
6to4 relay range (192.88.99.0/24), which the check already blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing-page rewrite kept only the "Browse the directory" link's
href, so no browser test loaded /discover any more, although the e2e
README says the suite covers discovery. Restore main's check that
/discover renders its heading.

The e2e README also lost the `npx playwright install chromium` step
when the live-suite instructions were removed; add it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ries

- Reject IANA special-use names and their subdomains (test, invalid, onion,
  alt, everything under arpa) besides localhost/local/internal/localdomain.
  RFC 6761 documentation names are rejected unless the check runs with
  --allow-documentation-names; only CI's placeholder step passes it, and
  railway.json keeps the strict check.
- Reject all IPv4-mapped IPv6 literals (outside global unicast), every Fetch
  Standard bad port, and a trailing-dot host name.
- Document the fail-closed deviations (192.0.0.0/24 and 2001::/23 anycast
  exceptions, NAT64, 6to4 relay) and pin the upstream tables in
  test/fixtures/origin-policy-sources.json, which the tests check the
  denylists against.
- Compare real paths in the CLI guard so a symlinked invocation still runs.
- Test the CLI itself: exit 1 with the [deployment config] message on a
  missing, insecure or non-public origin; exit 0 and the success line on a
  valid one; the symlink path; and that importing the module is silent.
- Pin Node 22.23.3 in .nvmrc and use it in CI, pin checkout/setup-node by
  commit SHA, run on ubuntu-24.04, and correct the stale private-SDK comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI installs Node from .nvmrc, but package.json engines still allowed any
Node 22, and the README asked for a manual RAILPACK_NODE_VERSION service
variable. Railpack applies engines.node first and then lets mise's
idiomatic version files (.nvmrc) override it (railpack ec5d727,
core/providers/node/node.go:419-494), so name the exact .nvmrc release
in engines (and the lockfile's root entry): every source Railpack reads
now gives 22.23.3, whatever their order.

test/node-runtime.test.mjs fails if engines, the lockfile or CI drift
from .nvmrc, or if another version file appears. The README and the
local guide now describe the pin, and the guide selects the .nvmrc
release instead of any Node 22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g matching

The spawned check inherited the whole test environment, so a runtime
warning it enabled reached the asserted stderr: with NODE_USE_ENV_PROXY=1
behind a proxy, Node prints an UNDICI-EHPA warning at startup and four CLI
tests failed. Run the children with NODE_NO_WARNINGS=1, and add a test
that injects an inherited warning (and checks that it does fire without
the guard) while the exact exit status and output assertions stay.

Also pin that only the exact --allow-documentation-names flag relaxes
the check: a misspelt flag or any other argument leaves it strict. And
check that the local guide runs the flagged origin check, since CI's
placeholder origin fails the strict one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gistry notes

- The SDK repository is public, so DACS_SDK_GITHUB_TOKEN is optional and
  only authenticates the clone. The README table and deployment notes and
  the setup-sdk.sh comment still called it the private SDK's token.
- The rejected exceptions inside 2001::/23 are not all anycast; list
  them (protocol anycast, AMT, AS112, ORCHIDv2, DRIP) next to the PCP and
  TURN anycast exceptions inside 192.0.0.0/24.
- Add the SRv6 SID block 5f00::/16 (RFC 9602) to the fixture's IPv6 rows.
  CPython v3.15.0's copy lacks it; it lies outside 2000::/3, so the check
  already rejected it. The fixture and README now say the snapshot is
  the pinned copies plus the registry rows they lack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nvmrc release

Railway doesn't pin which Railpack release builds the service, so the
engines-then-mise precedence the README relies on could change without
notice. Check the outcome instead: scripts/check-node-runtime.mjs exits
non-zero unless the running Node is the .nvmrc release, and the Railway
build command, start-railway.sh and CI all run it first.

The version-file guard now covers every mise config file and glob that
Railpack v0.41.0 hands to mise (mise/, .mise/, .config/mise*, mise.*.toml,
conf.d), not just five names. The README cites the v0.41.0 tag instead
of an untagged main commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The origin-policy fixture claimed a complete registry snapshot, but it
lacked the IPv6 Dummy Prefix (100:0:0:1::/64), eap.arpa, eap-noob.arpa
and the individual reverse-mapping zones. It also accepted the globally
reachable AS112, AMT and Direct Delegation AS112 blocks, even though the
README says special-purpose blocks are rejected.

- Reject 192.31.196.0/24, 192.52.193.0/24, 192.175.48.0/24 and
  2620:4f:8000::/48 with the other protocol-only blocks.
- Pin both IP registries in full through Wireshark's IANA-generated
  tables. A new test requires every registry row to be rejected at both
  edges and traced to a fixture row.
- Record each pinned copy as a raw URL at a fixed revision, with its
  sha256 and line ranges. scripts/verify-origin-sources.mjs
  (npm run verify:origin-sources, needs network) re-fetches them, checks
  the hashes and compares the re-extracted rows with the fixture.
- Add hard-coded vectors for invalid, onion, alt and eap-noob.arpa.
- Add a test for the CLI guard's realpath failure branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test/directory-separation.test.ts fails if a gateway route, component,
  script, package script, Railway/CI/Playwright setting or sitemap entry
  comes back. It also checks that the sitemap's static routes are exactly
  the Directory's own pages.
- Move the landing page's summary strip into homeCatalogSummary. Unit
  tests now cover the indexing, indexed-empty and populated states, the
  verified-only deal count, the 59/60-minute boundary and the clamp for a
  catalog timestamp in the future.
- The home e2e test now asserts /discover's URL and title. The landing
  page shares /discover's heading, so the old check could not fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The local workflow guide still said the SDK clone might need GitHub
authentication; the repository is public. playwright-report-live/ is
no longer produced now that the live suite is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The names snapshot traced each name only through Unbound's and Knot
Resolver's lists, and eap.arpa appears in neither, so nothing pinned
it. Pin IANA's Special-Use Domain Names registry XML itself, byte for
byte from a mirror of rsync.iana.org::assignments at a fixed commit,
and make verify:origin-sources require the fixture's special-use and
documentation names to equal the registry's names in both directions.
eap.arpa now cites RFC 9965, which also deprecates eap-noob.arpa.

verifyOriginSources takes an injected fetcher, and a new offline test
checks it against small in-memory copies with real sha256s: the
matching fixture verifies, and flipped or dropped registry rows, a
missing CPython row, missing or extra names, a dropped port, a wrong
sha256, changed bytes and HTTP 404 are each reported. The parser test
now has IPv4 and IPv6 Wireshark rows whose forwardable and global flags
differ, so reading the wrong column fails.

The Wireshark copy holds every unexpired row and records an N/A
Globally Reachable as false; say so, and note that the two terminated
blocks IANA still lists are rejected anyway. Document running the
verifier with --use-env-proxy behind a proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's Node always matches .nvmrc, so the CLI's exit 1 branch and the
start script's errexit never ran: a check that exited 0 on the wrong
release, or a start script without set -e, still passed. Run copies of
check-node-runtime.mjs and start-railway.sh in a temp app whose .nvmrc
pins 0.0.0, and assert exit 1, the [deployment config] message, and
that start:railway stops before the data directory, the indexer and
the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The summary helper was tested, but app/page.tsx could bind a stat to
the wrong field or label and every test would still pass. Render the
page against pending, indexed-empty and populated catalogs saved in an
isolated data directory, with the clock fixed, and assert the four
value/label pairs; the populated values all differ. Also pin the
page's link targets and its canonical URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y-demo

Separate the gateway demo from the Community Directory
Publish unavailable metrics for new and cached catalogs, refuse rate and score queries, and label observed history while keeping individual evidence inspection.

Co-Authored-By: Codex <noreply@openai.com>
Check populated history, aborted outcomes, cached numeric metrics and refused queries through APIs and rendered pages. Add an isolated Playwright lane for browser validation.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
DACS-4 SB-2 (pinned Standard d45c0a29) groups the whole presented evidence
set by settlement id before counting. A group claimed by two or more
(jobId, phaseIndex) tuples counts only through settlement-side binding
authority, and MUST NOT pick a winner by observedAt. The Directory
verifies no such authority, so it now excludes every member of a
collision group instead of crediting the earliest producer timestamp.
Repeats of one tuple stay idempotent, and hash spelling variants
(0x prefix, upper case) group as one settlement.

The collision test now expects the group to be void. A new test loads
three sb2-collision-authority-v0.8 vectors (source commit, path and
sha256 recorded) and checks that a backdated or tied claimant never
counts in either input order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TP-1 is now defined in-tree (dacs-directory/docs/TP-1.md). The file
declares the null-rate and refused-filter behaviour, and the phaseIndex
source used for settlement grouping, as open nonconforming deviations
in the Standard's ImplementationDeviation shape. The README and INDEX
link to it, and the code no longer cites an unpinned decision id.

Contract and version signals:
- The ListingSummary reputationHint schema requires every field the
  pinned SDK catalog reader requires (categoryScope, bundleCount,
  windowStart, windowEnd, computedAt). Counts and times are bounded to
  non-negative safe integers.
- The manifest reports dacsDirectoryVersion "2" and restrictions
  ["TP-1"], and the OpenAPI document is 2.0.0, because two DACS-1
  §6.3.6 parameters were removed.
- The unversioned schema URL is no longer served as immutable.

Projection:
- A cached hint that the SDK reader would reject (missing count,
  non-prefix scope, reversed or non-integer window) falls back to the
  synthesized hint.
- Rate, rating and score keys are dropped at seller, deal and listing
  level as well.
- Writers (index-now, reindex, the fixture seed script) read the stored
  catalog through loadStoredCatalog, so a read-modify-write no longer
  persists null rating values.
- /verify shows a recorded outcome as a neutral observation.

The tests check the exact bundleRefs and currency totals through
loadCatalog and the seller API, every refused metric key, the
exact-category count, the emitted hint against the schema and the SDK
reader, the manifest and OpenAPI literals, and the empty store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The indexer now projects each success-evidence TxRef to its DACS-4 SB-1
settlement-tx-id on both the current and the legacy path: demos and the
legacy { rail: "demos", txHash } give demos:{txHash}, evm-event and
x402-event give evm:{chainId}:{txHash}:{logIndex}, and solana-instruction
gives solana:{cluster}:{signature}:{instructionIndex}. A deal citing a
reference that cannot be projected without ledger data is not countable.

SB-2 grouping moves to settlementUniqueness.countableDeals, which seller
reputation, indexed listing hints and the read projection all use. It
groups the complete admissible set before the window, and keys claimants
by deal identity so two records for one tuple also collide. A served hint's
bundleCount is recomputed from the retained records, so a cached count can
no longer include a voided collision.

A cached hint's categoryScope must also be a canonical string (trimmed,
NFC, no C0 controls or DEL), as the pinned SDK reader requires; the
published schema states that with a pattern.

docs/TP-1.md describes the projection and declares the missing SB-1
ledger-event check as an open nonconforming deviation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to 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.

1 participant