Skip to content

Align Directory listing admission with current SDK - #85

Draft
randomblocker wants to merge 21 commits into
mainfrom
agent/sdk-listing-alignment
Draft

randomblocker wants to merge 21 commits into
mainfrom
agent/sdk-listing-alignment

Conversation

@randomblocker

@randomblocker randomblocker commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Align Directory listing admission with current SDK

Head: d0da471 (branch work, base main at d1de29d)

Summary

  • pin the Directory to dacs-sdk 3904d704fb540e6e1d7dd502a0be29f20569c92d, which declares DACS-Standard d45c0a292006b2fd2e40d2dbe0cd7ed518ad0f20 (dacs.standardRevision), and use the SDK's normative Listing shape/signature and current publication/reader gates. scripts/setup-sdk.sh now refuses an SDK build that is not marked as built from that commit, or that declares another Standard revision
  • admit a current Listing only when the SDK's isListing accepts it, including DACS-4 DPA-1: an attested-payload pipeline needs a structured verificationMethod from the closed DACS-2 registry (isVerificationMethod); failures are excluded with the public VERIFICATION_METHOD_INVALID diagnostic
  • expose admitted records as authenticated catalog candidates with transactionReadiness.disposition = unassessed. The SDK grants readiness only through its ordered Listing reader (isVerifiedListingAdmission), which the Directory does not run, so the catalog, seller and inspection projections always publish that disposition and never republish a stored one
  • make browser publication a staged exact-artifact flow: identity signature → exact unsigned Listing preview → Listing signature → broadcastable transaction. No transaction is returned for a Listing signature that does not verify
  • publish DACS-4 §9.4.2 registry rail IDs (demos-native:DEM, x402:default, ap2:stripe-paymentintents) with their registered phase handlers, resolved through this release's disclosed PA-1 in-code table (pa1-in-code). The pinned Standard publishes the v0.1 registry as a table, not as signed definitions, so this is not a canonically anchored result. Refuse attested-payload publication when the browser cannot prove the seller runtime's DPA-1 production capability
  • require canonical transaction inclusion, the block the configured Demos node reports as BFT-confirmed, exact metadata/native readback, and a finalized AnchorReceipt in the SDK's portable Demos form before catalog registration. The receipt must pass the SDK's CORE §5.1 isAnchorReceipt gate, be finalized/established, and bind the exact logical/native address, content hash, writer and transaction
  • treat a storage not found, a failed body read, or a response without an artifact as indeterminate (DEMOS-MAPPING §A.2, DACS-1 RB-6): a known verified or owner-anchored revocation marker that cannot be read withholds its listing instead of letting it be republished active
  • persist and render public-safe Listing rejection diagnostics with Listing ID/version and republish guidance
  • publish revocations under the SDK's §6.3.6 field name revocation (CatalogListingSummary.revocation), carrying the exact presentedBy bytes RB-2 compares, migrating persisted revocationBinding summaries once
  • add a read-only deployment acceptance command for the actual three malformed live anchors, their replacement anchors, the reindexed catalog, and the full SDK reader
  • retain normative revocation fields/migrations, strict current-vs-legacy handling, and the isolated SDK server/browser compatibility seams

Merge with main (#92–#94)

main now carries bounded verification work, owner-keyed deals, separate retry classes and malformed-artifact containment. This branch keeps that design and applies the admission changes on top:

Why

The live Directory accepted current-looking Listings whose offering.deliverable.verificationMethod was a string, while the current Standard and SDK require a registered structured variant. Those candidates could be discovered but were rejected by an independent SDK buyer before payment. main still checks only that offering.deliverable is an object.

Catalog admission is also narrower than transaction admission: the Directory can authenticate Listing shape, identity, signatures, hash, owner, and revocation observations, but it does not possess every buyer/session policy or runtime capability. The public contract now says that explicitly rather than calling candidates transaction-ready.

The prior browser flow could build a pay-bearing Listing without SDK rail/payload publication gates and register it after read visibility alone. It also did not show the exact server artifact before its Listing signature.

Scope and limits

  • Finality. The Directory checks that the block carries the node's validator-signature shape (a non-empty map of validator key to Ed25519 signature) and a 64-hex block hash. Like the pinned SDK's Demos adapter, it does not verify the validator signatures, the eligible validator pool or the 2/3+1 quorum, so a finalized receipt rests on the configured Demos node's report. The node endpoint and build are not pinned.
  • Rails and networks. The catalog does not evaluate acceptedRails[].parameters (for example a network or chainId an artifact signs). Network and chain selection stay with the transacting SDK: under DACS-1 LRR-6 the orchestrator selects and pins one complete PaymentRailRef at session start and applies RAV-R1..RAV-R5. Catalog discovery is never transaction permission (transactionReadiness: unassessed). The Directory's own publisher emits only registry rail IDs without parameters.
  • Not rejections. A Listing outside its signed validity window, or a storage response above the read-path byte bound, leaves the catalog without a rejection diagnostic: the first is valid but not currently effective, and the second is never read, so there is nothing safe to attribute.

Impact

After deployment and reindex, malformed current Listings are observed but excluded from the active catalog with stable seller-facing diagnostics. Existing valid candidates remain discoverable but are explicitly marked transaction-readiness-unassessed.

Catalog JSON consumers that read revocationBinding must read revocation (the SDK's §6.3.6 name; the SDK catalog client rejects a revoked summary without it).

Listings previously published through /register named pay-dem / pay-x402 as rail IDs, which registry-resolving readers reject; their sellers should publish a new version from /register. New browser-published storage-program and entitlement Listings use registry rail IDs, are signed exactly as previewed, and are registered only after finalized write evidence plus independent content verification. Attested-payload sellers must use their current SDK runtime so the exact production method can be proved before any payment-bearing publication.

An existing deployment that uploads a prebuilt SDK (railway up . --no-gitignore) must rebuild it once with npm run setup (or the documented local workflow) so vendor/dacs-sdk/dist/.dacs-sdk-revision exists.

The three existing x402 Listings remain excluded until their sellers publish conforming newer versions. This PR does not loosen the SDK, publish those replacement artifacts, deploy/reindex Community, or make the Directory depend on a gateway.

Deployment acceptance

After the three sellers publish replacements and the deployment is reindexed:

NEXT_PUBLIC_DIRECTORY_URL=https://directory.example \
DACS_LISTING_UPGRADE_REPLACEMENTS='[...]' \
npm run check:listing-upgrade

The command is read-only: it never contacts a gateway and never writes, reindexes, purchases, or invokes an agent.

SDK packaging note

The current SDK top-level barrel statically re-exports optional rail and substrate adapters, so it is not browser-bundle-safe without unrelated peers. Community imports the required public names through isolated compatibility modules until the SDK publishes safe subpaths.

Validation

Run offline against the vendored SDK at 3904d70 on Node 22.23.3:

  • npm run typecheck: passes
  • tests: 322 passed, 0 failed, 0 skipped, across every test/*.test.ts and test/*.test.mjs file. Run as node --import tsx --test --test-concurrency=2 in two batches, because the sandbox forbids the tsx CLI's IPC socket
  • new vectors include: storage 404/500/400/truncated/malformed/no-artifact reads of a known revocation marker; RB-1/RB-4 marker authorization; the Standard rail registry; node2-shaped finality reads with transactionRef, and recovery records that keep it; RB-2 bindings checked by the SDK's checkListingRevocation; injected key-resolver and identity-comparison boundaries; setup refusal of unmarked or mismatched SDK builds
  • npm run build and npm run test:e2e: not re-run in this round

🤖 Generated with Claude Code

random block and others added 17 commits August 15, 2026 20:40
Brings #92-#94 (bounded verification work, owner-keyed deals, malformed
artifact containment) under #85's SDK-aligned listing admission.

Conflicts, resolved semantically:
- store.ts: keep both startup migrations (#85 listing_rejections
  coordinates; #94 scan_runs omission counters, rejected-retry classes,
  revocation_candidates table).
- chain.ts: keep #94's boundedJson guard on storage reads and #85's
  extended readback fields (storageAddress, metadata, tx refs).
- indexer.ts: #85's admission (verifyListingResult with rejection
  diagnostics, validity window, `revocation`, transactionReadiness) over
  #94's bounded, owner-first revocation candidate batch and progress.
- reindexCore.ts: #94's design (candidates live in SQLite, not scan
  state) with the public `revocation` field name.

Semantic fixes the clean auto-merge did not cover:
- verifyListingResult fails closed instead of throwing, keeping #94's
  "no canonical form" containment for every caller.
- verifyListingRevocation: the pinned SDK's stripSignature now rejects
  non-canonical JSON, so it moves inside #94's existing guard.
- scan.ts: SDK f68fed1 renamed the MVP agreement predicate; use
  isLegacyMvpAgreementDocument (byte-identical to 44d8ff2's
  isAgreementDocument that #94 was written against).
- #94 tests: legacy listing fixtures use the SDK legacy-MVP string
  signature, current fixtures carry a DPA-1 verificationMethod, the
  revocation field is `revocation`, and the depth sweep follows the
  pinned SDK's 64-level canonical bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verification method: admission already delegates to the pinned SDK's
isListing (DACS-4 DPA-1) and isVerificationMethod (closed DACS-2
registry). Pin each failure mode with a test: missing, free-form string,
unregistered kind, registered kind with missing or malformed parameters,
attested phase on a non-attested deliverable, and an invalid method
outside an attested phase. Every registered variant is admitted, and
the indexer excludes failures with VERIFICATION_METHOD_INVALID
diagnostics while admitting the conforming listing.

Transaction readiness: the SDK grants it only through its ordered
Listing reader (isVerifiedListingAdmission), which the Directory does
not run. Catalog, seller and inspection projections now always publish
the Directory's `unassessed` disposition instead of republishing a
stored value.

Anchor receipt: registration admits a listing only on a receipt that
passes the SDK's CORE §5.1 isAnchorReceipt gate, is finalized and
established, and binds the exact logical/native address, content hash,
writer and transaction. Confirmation tests now also cover the
finality-pending and metadata-mismatch paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The metadata-mismatch case also broke native/transaction metadata
equality, so a mutant dropping the envelopeHash comparison survived.
Add the case where both metadata copies agree but neither hashes the
anchored listing envelope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f68fed1 refused Listings that the current SDK and Standard admit:
APR-1 pay-alternative pipelines and CORE CF-5 nesting deeper than 64
levels. Move the pin to the current SDK revision.

- The modules the Directory loads from the SDK reach only Node
  builtins at 3904d70, so none of the SDK's six new runtime
  dependencies is needed by the Directory.
- The SDK's ClaimReference parser now reaches the browser bundle and
  imports node:net and node:url for domain hostnames. Add WHATWG
  browser shims next to the existing node:crypto and node:util ones,
  with a parity test against Node.
- The signature depth sweep returns to main's 126-level threshold
  (CF-5 depth 128) and keeps 61-64 as guards against the old bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CORE B.1 CF-3 makes ClaimReference parameters advisory: identity is the
scheme and identifier. The SDK's isListing authorizes the signer that
way, but the Directory's Demos resolver accepted only the
parameter-free form. A Listing signed as did:demos:agent:<key>?channel=x
therefore passed the SDK and failed here with LISTING_SIGNATURE_INVALID.

claimRef.ts now reads exact CF-2 parameters and returns the
parameter-free identity, as the SDK's parseDemosAgentClaimReference
does. It mirrors that function instead of importing it because the SDK
identity tree loads node:net and this module also runs in the browser;
test/claim-ref.test.ts pins parity with the SDK on 35 inputs. Key
resolution, signer authorization and seller matching all use the
identity, so sellers stay keyed by the parameter-free claim, and
registrations still require it. The SDK validation view keeps the
parameters and only repairs the scheme token.

Cross-version conformance cases, all refused at the previous pin:
signed pay-alternative Listings (and SDK-refused incoherent ones),
nesting through 128 levels, advisory parameters on each signed role
(and non-canonical parameter forms refused). An indexer-level case
covers all three and an expired Listing, which had no indexer test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Validate the resolved object directory and reject shared or redirected object stores before fetching. Scope Git path overrides to the selected checkout and exercise the documented workflow across standalone clones and rejected layouts.

Co-Authored-By: Codex <noreply@openai.com>
The browser publisher stored the wallet's hex signature as the Listing's
signature value. CORE §B.7 SIG-6 defines that value as unpadded base64url,
so SDK readers decoded 96 bytes and refused every Listing published through
/register, while the Directory's hex-first decoder admitted them. Store
base64url (the identity presentation too) and decode a current Listing's
signature strictly as SIG-6 Ed25519.

Apply isListing to the exact received bytes instead of a scheme-lowercased
copy, so an uppercase DID: token inside a signed artifact fails here as it
does at the SDK reader's first step.

Hex-signed versions now leave the catalog but still occupy their owner-held
program names. The publisher steps past up to four such versions so sellers
can republish. check:listing-upgrade now verifies signatures and identity
presentations against the signer's key instead of comparing the artifact
with itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unreadable revocation marker made findValidListingRevocation return
null, and the indexer then emitted a fresh `active` summary. SDK readers
take that as established revocation absence, so a transient outage could
turn a revoked Listing back into a verified one.

A revocation this catalog already published now stays in force while its
marker cannot be read. When the node cannot serve a verified marker that
has no published binding, or a candidate anchored by the listing's owner,
the listing is withheld for that pass. Candidates anyone else anchored, and
candidates that are absent rather than unavailable, cannot withhold a
listing. A new readAnchorOutcome separates node failures (network, 5xx,
429) from answers that hold no artifact.

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

The finalized AnchorReceipt gates only the /register browser flow:
registrations do not carry it, POST /api/dacs/register accepts pointer sets
from any client, and catalog admission rests on the indexer re-reading and
re-authenticating every anchor. Say so in the README and the
prepare-registration route comment instead of describing the receipt as a
catalog registration requirement.

Also document SIG-6 admission on exact bytes, independent verification in
check:listing-upgrade, the node:net/node:url browser shims, revocation
handling during marker outages and stepping past unauthenticated versions.
The local workflow guide now rejects symlinked .git/shallow and FETCH_HEAD
and states its Git 2.31 requirement.

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

@cX3po cX3po left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked draft head 0b61ec4cc8f1bedbd965520f9f03595c89b41bc5, SDK pin 3904d704fb540e6e1d7dd502a0be29f20569c92d, offline on Node 22.23.3: 305 tests passed, none failed or skipped; typecheck passed.

Using our public arena practice seller's verification-summary title, a shortened description and 0.01 DEM price, I constructed a normative Listing with a disposable Ed25519 identity and the SDK's signComponentArtifact. This is a seller-shaped synthetic Listing, not an export from our standing seller or a claim that its arena practice protocol implements the kit's paid lifecycle.

The baseline was indexed as a catalog candidate with transaction readiness unassessed. The indexer excluded all five variants: a non-Demos claim scheme (LISTING_SIGNATURE_INVALID, cause not isolated), missing price (NORMATIVE_LISTING_INVALID), expired validity, a different signing key (LISTING_SIGNATURE_INVALID), and a description exceeding 256 KiB. Expiry passed signature validation; the indexer excluded it without a rejection diagnostic, and its exported validity predicate returned false. The oversized artifact was excluded without a diagnostic and failed the read-path byte bound. Those last two causes are inferred from the measured predicates, not emitted diagnostic codes. A string verification method was refused with VERIFICATION_METHOD_INVALID; structured {kind:"self-signed"} passed. If a seller emits a string method, this SDK pin refuses that Listing; this run did not test generated-kit runtime behavior.

One boundary to clarify: signed acceptedRails[0].parameters containing network:"mainnet", chainId:"ethereum" still entered the catalog as unassessed. These are extension parameters, not proof of actual wrong-chain execution; the malformed claim-scheme probe does not establish network refusal. Is network selection intentionally left entirely to the SDK reader/rail policy? I cannot confirm general wrong-chain rejection from this run.

The public testnet RPC root returned HTTP 502 at 18:36 UTC on October 14. Storage responses were mocked, with outbound networking disabled during tests; no anchor was published and no live seller, finality, runtime capability or payment flow was tested. This is a COMMENT on a draft.

— PATH-OS (cX3po)

randomblocker and others added 4 commits October 15, 2026 05:14
…zation

DEMOS-MAPPING A.2 gives an ordinary storage API not-found the SR-2
disposition indeterminate, and DACS-1 RB-6 makes transport failures and
integrity mismatches indeterminate too. readAnchorOutcome now reports every
read that yields no readable artifact (404, any non-2xx, a failed or
malformed body, a response without an artifact object) as unavailable, so a
known verified marker or an owner-anchored candidate withholds its listing
instead of letting it be republished active.

Adds RB-1/RB-4 vectors for markers whose signature object, algorithm,
signer, value, listingId, listingVersion or content hash is wrong, a marker
carrying a reason, and a prior binding that must stay with the listing it
revoked. malformed-artifacts now clears queued candidates between tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publisher used phase-handler names (pay-dem, pay-x402) as rail IDs, so
readers resolving the DACS-4 9.4.2 registry rejected its Listings as
unknown-rail. It now emits demos-native:DEM and x402:default with their
registered handlers, accepts the registry grammar on input, and the catalog
projection keeps uppercase registry IDs for the 6.3.6 rail filter.

rail-registry.test checks the table against an independent copy of the
pinned Standard's registry and resolves a /register-built Listing against
it. build-listing adds vectors that refuse a transaction for an invalid
listing signature and allocate versions per listingId.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- hash the identity presentation over the SDK's identityBundleHash
- publish RevocationBindings with the exact presentedBy bytes
- fall back to block one when a node answers block zero with an error,
  read a known transactionRef by hash, and normalise JSON-string
  signatures so receipts carry the SDK's signed-transaction proof hash
- require the node's validator-signature shape and a 64-hex block hash;
  validator signatures and quorum are still not verified
- use Node's IPv6 zone grammar in the browser isIP shim

Tests use node2-shaped fixtures and pin the presentation-kind, injected
key-resolver and identity-comparison branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup-sdk.sh accepts a packaged SDK build only when it carries the pinned
commit marker the source build writes, checks the source checkout is a
clean HEAD at SDK_REV, and on both paths requires the SDK to declare the
pinned Standard revision. Public Standard links name that revision instead
of main, and the README scopes the rail and finality claims.

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.

2 participants