Repository navigation
Fix gaps in recently merged Directory code found by a full sweep - #95
Draft
randomblocker wants to merge 17 commits into
Draft
randomblocker wants to merge 17 commits into
randomblocker wants to merge 17 commits into
Conversation
…nforming path (SIG-6) CORE §B.7 SIG-6 says a signature value is unpadded Base64URL. A verifier must reject every other spelling and must not auto-detect by trying decoders. The current-listing, revocation-marker, evidence-graph component and bundle verifiers all accepted hex, 0x hex, padded and standard Base64. Only BundleBinding was strict. - Add src/catalog/signatureValue.ts: one strict decoder used by every current-profile envelope, the declared-encoding legacy import from the standard, and an explicit legacy reader used only by the legacy-sdk-v0.1 paths (draft SDK listings and the SDK bundle policy). - The register page now re-encodes the wallet's hex listing signature into the canonical value before anchoring, as SIG-6 requires of producers. Listings already anchored with a hex value must be re-published; their content hash does not change. - Per-claim presentation signatures are bare strings, not SIG-6 envelope values, so they keep the existing decoder. - Vendor the pinned signature-value-encoding and revocation-binding vector files with a Directory SPEC_PIN (v0.4, 4bb9e48). Switch the test helpers to base64url and pin the seven non-canonical spellings on each verifier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-3 rules - Publish a listing only once validity.notBefore has passed. Validation step 3 is notBefore ≤ now ≤ notAfter; only notAfter was checked. - Add the negotiate-sealed-envelope-procurement phase kind and apply the full PS-3 matrix. negotiate-fixed-price takes fixed, negotiable or metered pricing; rfq takes negotiable or metered; both sealed-envelope kinds take auction. Negotiable pricing with fixed-price negotiation and auction pricing with procurement were rejected before. - Measure the 16 KB LR-2 cap on the canonical form with the signature omitted, not on the raw JSON with its signature. - Accept only the DACS-4 auction selectionRule enum or a content-hash-bound rule-ref; any string was accepted before. - Bound a revocation marker's revokedAt by the CORE numeric profile. Negative values are numbers DACS-1 allows. Fractions are still refused, because the pinned SDK canonicaliser cannot form them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cation check is incomplete DACS-1 RB-5 and RB-6 say an incomplete RB-4 check or a transport failure is indeterminate, never absent. RB-3 says a revoked record keeps its binding. The indexer had only two outcomes: if a verified marker could not be read on a pass, or a valid marker sat behind unread or unexamined candidates, it republished the listing as active with a fresh catalogObservedAt. Readers of the catalog then accepted new sessions on a revoked listing. - Compute a three-state RevocationCheck per listing. revoked: a marker verifies this pass, or the previous record was revoked; a later pass never withdraws a verified revocation. indeterminate: a candidate was unread or is still pending. absent: every candidate was examined and rejected. - The catalog schema allows only active and revoked, so an indeterminate listing is withheld for that pass. It still resolves past sessions and identity. - Add readAnchorResult, which separates a definitive answer (404, 401, 403, 400, or non-object or oversized content) from a transport failure. Only a transport failure leaves a candidate unread. A deleted or hidden candidate is rejected, so it cannot keep a listing withheld for good. - Update the README's revocation paragraph, which still described a 16-candidate retention window that no longer exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ored value changes A rejected candidate went back to pending only when its content hash changed. The content hash omits the signature envelope, so a marker re-published in place with a corrected envelope was never examined again. That includes the canonical re-encoding SIG-6 asks producers to publish. The same marker at a fresh locator revoked the listing at once. Admit candidates with a hash of the whole stored value, envelope included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…signed agreementRef, not program names DACS-5 §10.4.2 makes a StorageProgram name an opaque, colon-free write input: "A consumer MUST NOT treat the name as a resolution key." The scanner found a discovered deal's agreement under the raw name dacs3:agreement:<jobId>. Conforming producers percent-encode that name (199 of the 209 agreement programs on node2 do), so their deals were never attributed to a seller and never counted. - A current-profile buyer bundle now resolves its agreement through the agreementRef it signs. The scanner reads that locator, checks the content hash, and requires a co-signed current agreement whose buyer is the bundle owner. The deal records the agreement locator. - Name-keyed duplicate invalidation in reindexCore applies only to deals attributed by name. Legacy SDK bundles keep resolving by their session anchor names on the labelled compatibility path. - Update the scanner header and the README, which described name-based classification for every profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…10.5.1) DACS-5 §10.5.1 says the deriver "MUST collapse the input to one authoritative bundle per jobId before partitioning". Canonically equal copies collapse, a fully signed copy takes precedence over lesser-signed divergent ones, and equal standing voids the jobId (BB-6). #94 keyed the dedupe on (buyer, jobId), so two buyers' fully signed completed copies for one seller and jobId counted twice. A conforming deriver gives zero. - dedupeVerifiedDeals keeps one record per bundle ref and per (jobId, bundleContentHash). Among divergent copies of a jobId it keeps a lone fully signed one; any other verified copy stays listed with reputationEligible false. - DealRecord gains fullSignatureStanding, set from the BB-6 inspection on the current path and from the SDK signature verdicts on the legacy path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ACS-1, DACS-3 and DACS-4 types DACS-1 validation step 1 is schema conformance, and LR-2 rejects a listing that fails any step. The verifier treated the deliverable, buyerRequirement, terms, rails and most phase parameters as any object, so a correctly signed listing with a malformed shape was published. - Check DeliverableSpec (all four kinds, including external), BundleRequirement and ClaimRequirement, ListingTerms, PaymentRailRef, requiredCapabilities, the optional offering members and validity.notAfter. - Check per-kind phase parameters: negotiate-rfq needs maxTurns >= 2 and a positive timeoutSec; both sealed-envelope kinds need commitDeadline, a revealWindow of at least 60 seconds, the auctionMode their kind requires, and a selectionRule equal to the auction pricing's (DACS-3 §8.4.3); pay-* steps need parameters; rate's `required` is a boolean. - Unknown members stay open-world, as before (SIG-5, §11.1.2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…graph to DACS-3 §8.5.2 and DACS-2 §7.7 The evidence graph accepted a co-signed agreement once its commitment artifact matched the listing pipeline. It did not check the currency, price, rail, deliverable, pattern or role direction against the pinned listing, so a correctly signed agreement on other terms could feed a completed deal. Vet records missed required members, their VerifyResultRef recipeVersion was never compared, and a record that did not pass could back a successful vet phase. - Add agreementListingConformance: §8.5.2 checks 1-4 and 7-9 in order, with CD-1 decimal arithmetic, the inclusive half-up band, the metered MTR-1..5 total, the fixed-price fallback of an RFQ step that declares fixedPriceFallback, and SE-8 role direction for procurement. Checks 5 and 6 need the CommitmentRecord's committedAt, which a bundle does not reference, so they are not evaluated. - Require VerifyResult's recipeVersion, method, reason and fetchedAt, and the composite record's bundleHash, requirementHash and generatedAt. Bind evaluatedParty and bundleHash to a session party, requirementHash to the listing's buyerRequirement, and each VerifyResultRef's recipeVersion to the resolved result. - A vet phase reported ok must rest on a passing record (VPC-4), and a session that reached its agreement needs a passing record for each party it vetted. - Admit graph artifacts up to the 128 KB DACS-5 storage soft cap; 64 KiB rejected valid bundles. Correct the storage-limit comments: the node and demosdk 4.0.13 accept 1 MiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roxy's locator guard
The in-browser strict-pass, limited and fail verdict lived inside the
VerifyDeal component, and the artifact proxy's only path guard
(chain.ts, /^stor-[0-9a-f]{40}$/) had no test, so either could regress
silently.
- Move the verdict into bundlePolicy.browserDealVerdict, unchanged, and
test each input: expected and registered parties, signatures, strict
refs, the registered copy role, and a missing registration.
- Test that the artifact route forwards only a native storage locator to
the node: traversal, query, uppercase, short, long and padded refs never
reach fetch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… hash DACS-4 SB-2 assigns a settlement transaction cited by two jobs to the earliest evidence observation, "ties broken by lower evidence hash". The deriver broke ties by jobId. Each settlement id now carries the content hash of the evidence that cites it, ties go to the lower hash, and jobId only breaks a tie between equal hashes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wer as terminal The node answers 400 INVALID_REQUEST for an address no program can occupy (kynesyslabs/node routes.ts). The scanner classed it as the transient STORAGE_RPC_UNAVAILABLE and retried it. Class it with STORAGE_NOT_FOUND; an unexplained 400 stays retryable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on the deal page The deal page read the delivery attestation at the seller's derived address and said "The seller signed this delivery attestation" before any check ran. The node does not check that an address matches its derivation, so another writer's record there was shown as the seller's. Show the card only when the record's storage owner is the expected seller, and say its signature still has to be checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…otes - README: name the Standard, SDK and node revisions the verdicts depend on and the node's storage-read contract; list ARTIFACT_REJECTED and its retry class; describe the binding ceiling as it is since #94 (32 per signer of each job and role, no total across signers). - SPEC_PIN: the did:demos:agent profile is Ed25519, so listings signed with ecdsa-secp256k1 or sr1-aggregate are not verified. - The SDK repository is public; the token is only for a private mirror (README, setup-sdk.sh, CI). - The sessionAnchorName import comments: upstream exported it after the pinned SDK_REV. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n sweep left untested A mutation sweep of the listing verifier and indexer showed most listing acceptance and revocation rules could be removed without a failing test. - listing-rules: every DACS-1 identity, offering, pricing, pipeline and validity rule and every schema member at its boundary, the per-claim presentation, the legacy-sdk-v0.1 envelope, the indexer's publication gate (readable, owner-bound, hash-pinned, in its window), and RB-3 retention scoped to the same listing version. - revocation-vectors: run the vendored revocation-binding-v0.3 vectors through the Directory's verifier with the vector's key, and pin RB-4's tuple and envelope checks with markers that are validly signed, so only the rule under test can reject them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The current-profile evidence graph and the BB-4..BB-7 binding path had hundreds of surviving mutants on shape, reference, signature and party checks. - evidence-graph-rules: one case per DACS-5 bundle shape, party, signature and fault-attribution rule, per top-level and nested reference rule, per DACS-3/DACS-5 artifact shape and signature rule, DACS-2 vet records, CA-5/CA-7 bindings, payee-bound payout coverage and the §8.5.2 inputs. Each mutated artifact is signed again so only the rule under test can reject it. - bundle-binding-rules: BB-4 members and envelope, the per-signer stored ceiling and its overflow, and BB-5..BB-7 side resolution. - indexer-binding-rules: the indexer resolves each side only through its role holder's bindings, records the deal unverified otherwise, and reports full signature standing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test and pin its strict policy The legacy-sdk-v0.1 deal path had no positive test, so any of its guards could be removed silently. The browser verdict uses the same policy. - An SDK-profile deal verifies end to end through the indexer, and each copy must be strict: both copies, the registered roles, both signatures, resolvable evidence and no outsider signature. - The strict ref policy, referenced-artifact signatures, the bundle's signature entries, registered deal and copy-role binding, the browser verdict's registered owners and the pinned cancellation terms, one case per rule. - A copy carrying the current bundleVersion "1" goes to the current verifier and is never retried as legacy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d SIG-6 decoder rules - identity-rules: a claim counts toward the reported tier only through a hash-bound, signed, passing VerifyResult from a trusted signer under a live, version-pinned recipe, inside its validity window, with its authority attestation; each rule withholds the tier when it fails. - reputation-rules: eligibility, the windowing basis, SB-2, neutral outcomes and rates, bundle references, seller ratings, observed volume and ST-10 neutrality. - signature-value-rules: SIG-6 encoding and decoding at every length, the declared legacy import and the legacy-sdk-v0.1 reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two full reviews of the DACS Directory code merged since 2026-10-08 (#87, #88, #92, #93, #94) found places where the Directory's verdicts differ from what the DACS Standard v0.4 requires, and rules no test pinned. This PR fixes them, adds a regression test for each fix, and adds rule tests for the rules a mutation run showed were unpinned.
Each fix's test fails when the fix is reverted. The typecheck is clean at every commit, and the whole suite passes at every commit (353 tests at the tip, up from 273 on main). Mutation testing over the nine modules these findings touch dropped the surviving mutants from 1,411 to 573. The remaining groups are listed at the end.
What changed, finding by finding
Revocation (DACS-1 RB-3, RB-5, RB-6)
active. The check is incomplete while a candidate marker could not be read because of a transport failure, or is still waiting its turn. The catalog has noindeterminatestatus, so the listing is held back until a later pass completes the check.revokedwith its binding, even if its marker later cannot be read. It is carried forward only for the same listing version.Deal attribution without program names (DACS-5 §10.4.2)
agreementRefits buyer bundle signs: the locator, the content hash, and a co-signed agreement whose buyer is the bundle owner. Program names are no longer used. Before, deals whose producers percent-encode the colons in program names (most deals on node2) were never attributed to their seller.Canonical signature values (CORE SIG-6)
0xhex, padded or standard Base64, whitespace and non-zero residual bits are rejected without trying other decoders. The legacy SDK profile keeps its own labelled reader.SPEC_PINrecords the Standard revision (v0.4,4bb9e48), and the conformance vector files are vendored byte for byte.Per-jobId reconciliation (DACS-5 §10.5.1)
Listing validation (DACS-1 §6.3.4, LR-2, DACS-3 PS-3, DACS-4)
validity.notBeforehas passed.negotiate-sealed-envelope-procurementphase is added, and the full PS-3 pricing table is applied.selectionRulemust come from the DACS-4 enum or be a content-hash-bound rule ref.revokedAtis bounded by magnitude only, as CORE requires.selectionRulemust equal the pricing's.Agreement and vet records in the evidence graph (DACS-3 §8.5.2, DACS-2 §7.5 and §7.7)
recipeVersionmust match the result it points to. A composite must be bound to a session party's bundle and to the listing's requirement, and a successful vet phase or a committed session must rest on a passing record.Browser and API decisions
browserDealVerdictand is tested input by input.Smaller fixes
INVALID_REQUESTis terminal (scan-addresses "storage failures are classified into public-safe operational causes").ARTIFACT_REJECTED, the pinned upstreams and the node's storage-read contract, and states the binding ceiling as it is. The notes calling the SDK repository private are removed, and the stale import comments are corrected.Rule tests from the mutation run
Wire and behaviour changes
There are no external users yet, but these are visible:
notBeforeis in the future, are no longer published.DealRecordgainsfullSignatureStanding, eachsettlementTxIdsentry gainsevidenceHash, and discovered deals gainagreementLocator.readAnchorResult(chain),hasPendingRevocationCandidates(store),browserDealVerdict(bundlePolicy),agreementListingConformanceandnormalizedHash(evidenceGraph), and thesignatureValuemodule.content_hashofrevocation_candidatesnow hashes the whole stored value, so existing rejected candidates are examined once more.Not in this PR
reindexCore.ts:143-146,bundleBinding.ts:108-127). The revocation-candidate queue has no cap (store.ts:127,257-271;scan.ts:186-189,552). Because an incomplete check now holds a listing back, a flood of junk candidates can delay its publication.revokedAt. The pinned SDK canonicaliser rejects every fraction.scripts/setup-sdk.shdoes not check the SDK revision.bundleVersion: "1", but the pinned SDK writes"1", so its deals take the current path and fail there.🤖 Generated with Claude Code