Repository navigation
feat(spec,demand-intel): kit-demand signal and fixed periodic public release layer (R44, PX-13) - #365
Merged
Merged
Conversation
… (R44) Interface work for ledger row R44 (pcc-painpoints -> pcc-kits), additive only: - demand.ts: UnmetReason / UnmetCapability and the server-owned DemandEnvelope.unmet field. The caller-input DemandEnvelopeSchema (used by /api/intent/ingest) is unchanged, so zod still strips a caller-supplied unmet list; ServerCapturedDemandEnvelopeSchema is the only schema that carries it. stripServerOnlyDemandFields() removes fulfillmentPath and unmet from any envelope that arrived from outside. - kit-demand.ts: KitDemandSignal (private; per capability key; internal unmet counts by evidence class plus a pointer to a private prior), with consistency refinements and a canonical 0x-sha256 digest; and toPublicOpportunityAggregate(), the only demand shape allowed to leave the server: allow-listed fields, banded counts, suppressed below k distinct verified requesters (k >= 5), never prior-only or proposed types. Tests: 29 new (synthetic fixtures only), including forged breadth, prior-only and proposed-type suppression, histogram consistency, strict keys, ingest-schema non-widening and input immutability. @pcc/spec 826/826 (baseline 797), tsc clean; consumers @pcc/demand-intel 23/23 and @pcc/intent-broker 13/13, tsc clean against the rebuilt dist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JBSkBhKBWPA9AeCJ7J5G7a
…als (R44 D3) UnmetDemandLens folds server-captured UNMET intents into one validated KitDemandSignal per capability key, merged with optional private priors. It is fail-closed against forged demand: - reads only first-party capture event types; intent.external_ingest (caller-asserted envelopes) is never read - counts an intent only when the server marked it unmet (ServerCapturedDemandEnvelopeSchema, fulfillmentPath "unfulfilled", non-empty unmet) - verified breadth counts only rows the server stamped actorType "authenticated_operator". On master ac86a40 every capture point (requests, negotiation, A2A, nl-query) takes its actor from the request body, so breadth is 0 and nothing clears the public k until the gateway records the authenticated principal (D2 contract) - firstSeen/lastSeen use the row's server timestamp, not the envelope's createdAt; exact Set counting, never HyperLogLog, near the k threshold - known types must arrive as CSD URIs; only no_capability_type slugs become proposed:<slug> Tests: 14 new (synthetic), including external-ingest exclusion, body-actor zero breadth, one-principal dedupe, window filtering, determinism across insertion order, and the seam into toPublicOpportunityAggregate (4 principals private, 5 public; volume alone never publishes). @pcc/demand-intel 41/41 across 5 files, tsc clean. Correction to 58c720a's message: it said "@pcc/demand-intel 23/23". That run had not loaded aggregator.test.ts (4 tests) because @pcc/store was not built in the worktree. With it built, the 27 pre-existing tests all pass against the spec change; @pcc/intent-broker 13/13 was correct. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JBSkBhKBWPA9AeCJ7J5G7a
…class counts (kits review #2506) Addresses pcc-kits' shape review of feat/kit-demand-signal (bus #2506): - KitDemandInternal gains distinctVerifiedRequestersByClass and the exact, cumulative distinctVerifiedRequestersAtOrAbove (strongest class per principal), plus urgency, requested-assurance-tier and country histograms (ISO alpha-2 or "unknown"; sub-country detail is dropped) and the window bounds. Schema refinements enforce every sum, the per-class bounds, monotonicity and the union bounds. - OpportunityAggregatePolicy gains minEvidenceClass (default and floor authenticated_order) and minWindowDays (default and floor 30). The projection counts only requesters whose strongest intent meets the floor, refuses any weaker policy, and reports countedEvidence. Bands follow the kits proposal: 5-9, 10-24, 25-99, 100+. - kitDemandSignalDigest now returns sha256:<hex>, byte-identical to the async util/canonical sha256(canonicalize(x)) (a test asserts this). - UnmetDemandLens emits the new fields; normalizeCountry is exported. Caveat stated in code: k-anonymity is a privacy floor, not a Sybil control. Until R28/R29 bind keys to identity, only funded evidence costs a forger money, and no capture point emits funded evidence yet. Tests: @pcc/spec 842/842 across 39 files (kit-demand 45), tsc clean; @pcc/demand-intel 46/46 across 5 files (unmet-lens 19), tsc clean; @pcc/intent-broker 13/13, tsc clean. All against the rebuilt spec dist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JBSkBhKBWPA9AeCJ7J5G7a
…ound-1 F1-F5) The round-1 cross-family review of #365 @e5fa22ba (pack 10) returned DO-NOT-SHIP as a public feed. This commit closes the pure-layer half of each finding. The publisher (no-parameter read surface, write-once release ledger, approved set) belongs to pcc-kits (#3405). F1 toPublicOpportunityAggregate has no policy parameter. One frozen PUBLIC_RELEASE_POLICY: k = 5, floor authenticated_order, 24 h grace. F2 Only canonical UTC calendar-month periods ("YYYY-MM"), released only after close + grace. Any other window, an open period, or a signal computed before its period closed throws. buildPublicRelease() returns one deterministic release with a sha256 digest. The lens gains computeForPeriod(). F3 UnmetCapabilitySchema requires the key form to match the reason: no_capability_type only as a slug, every other reason only as a CSD URI. resolveCapabilityKey checks the reason first and can take a registered set. The projection emits only exact members of the publisher's approved set that pass isPublishableCapabilityId (slug at most 40 chars, at most 3 hyphens, no run of 4+ digits). The release records the approved set's digest. F4 asOf is gone. The aggregate (now v1) carries the period label. F5 CallerDemandEnvelopeSchema (no fulfillmentPath, no unmet) for caller input. The ingest route switches to it in #387. Tests cover the verdict's adversarial cases: two k values, sliding windows at millisecond precision, the CSD-shaped no_capability_type counterexample, and one unverified late intent. Each guard was also mutation-checked locally: removing it fails the suite. spec 865/865, demand-intel 55/55. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
added a commit
that referenced
this pull request
Sep 28, 2026
…t parses the caller schema (PX-13 round-1 F3, F5) Follows the merge of feat/kit-demand-signal @6377dd5d (the #365 round-1 fixes). F3 computeUnmet lists an entry only if it passes UnmetCapabilitySchema: a registered type only by its canonical CSD URI, a type with no CSD only as no_capability_type with a slug. An unmet type that cannot be keyed that way is counted as unkeyed, not listed: no capacity or no tier for an uncatalogued type, a registered URL that is not a canonical CSD URI, or a name that does not slugify. Such an intent is still marked "unfulfilled", never "auto". F5 POST /api/intents/ingest parses with CallerDemandEnvelopeSchema, which has no fulfillmentPath, unmet or unmetTruncated. The strip stays as a second guard. The lens header now describes D2's two actor strengths. The seam tests now run capture -> lens (computeForPeriod) -> buildPublicRelease. A type publishes only at 5 proven principals, for a closed period, and only when the publisher approved its CSD. Key holders and proposed types never publish. spec 865/865, demand-intel 55/55, gateway 3026 passed / 6 skipped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
added a commit
that referenced
this pull request
Sep 29, 2026
The consumers acked this on the bus: readmodels (#3623), adk (#3785) and refvertical (#3571, #3792). - A2: OpportunityDTO.capabilityContractDigest pins the exact CSD revision. It is REQUIRED on a funded funded_offer, because the accepted plan pins it; optional on kit_build_request; forbidden on demand_aggregate. - A3: evidence.requiredEventClasses: string[] becomes evidence.requiredPrimitives: CsdEvidencePrimitiveRef[], the same grammar as a CSD's evidence refs and setup's EmitterDecl emits[]. Each id must be active in EVIDENCE_PRIMITIVES, so the ADK's provenance planner compares supply against demand with no mapping table. - A4: the manifest header states two conventions. The provenance-recipe mediaType is application/vnd.pcc.provenance-recipe+json;v=1, and compatibility.interfaces uses the kernel's AdapterType names, never "mock". There is no shape change. - A5: a demand_aggregate carries releasePeriod (YYYY-MM), required there and forbidden elsewhere. asOf stays the READ time for every kind (readmodels' counter), so it never says when an intent happened (#365 F4). - A6: artifact names must be safe relative POSIX paths. Before, the schema accepted '../../etc/passwd', a path traversal for any installer that writes artifacts by name. Refvertical's real R46 kit already complies, so its kitDigest is unchanged. - One CSD_CAPABILITY_URL_PATTERN is now shared by the kit manifest and OpportunityDTO. Golden kit digests are unchanged (A6 only validates). kits-contracts passes 26/26 (5 new). With the source changes reverted, 8 fail. The full spec suite passes 823/823 and tsc is clean. A1, the OperatorBindingDTO part, follows separately: operator-ux, its last acker, is paused (steward #4029). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FT8WYWkfig4KNcxtMqvMuj
… round-2 F3) The round-2 cross-family review (pack 38) closed F1, F2 and F4 and asked for one F3 fix before merge. Projection checked membership with the caller's approvedCapabilityTypes.has(), while approvedSetDigest iterated the same object separately. So a Set whose has() or iterator disagreed with itself could publish an ID the digest never committed. It was reproduced first, at 6377dd5, with a throwaway test: an approved Set with has = () => true published pcc://capabilities/secret-us-ca-sf/v1, while the digest committed only the one iterated ID. Now the approved identifiers are read once: only strings that pass isPublishableCapabilityId, deduplicated and sorted, held in a fresh native Set. That one snapshot drives both membership and approvedSetDigest, in toPublicOpportunityAggregate and in buildPublicRelease. Tests: a lying has(), an iterator that changes between passes (read exactly once), and a mixed set (only publishable strings are committed). Mutation-checked: all 3 reversions fail. spec 868/868, demand-intel 55/55. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu
marked this pull request as ready for review
September 30, 2026 19:18
LamaSu
added a commit
that referenced
this pull request
Oct 1, 2026
…ease (astra 112b A)
Slug syntax is not a privacy boundary ("alice-smith" passes
isPublicCapabilityUrl), so a demand_aggregate now has to name a capability in
an approved public set, and has to name the release record it came from.
- BUILTIN_PUBLIC_CAPABILITY_URLS: the CSD urls compiled into @pcc/spec
(loadBuiltinCsds), filtered, sorted by code unit, frozen. That is 8 urls:
document-print-and-mail/v1 is a draft workflow CSD that loadBuiltinCsds does
not register and the package build does not ship, so it is not in the set.
- publicCapabilityUrls(activeKitCsdUrls): built-ins plus ACTIVE kits' csd urls;
throws on any entry that is not a public capability url.
- approvedSetDigest(urls): same construction as #365's approvedSetDigest, pinned
by a golden vector (also checked once against #365's real buildPublicRelease).
- DemandAggregateDTO.releaseDigest is required.
- opportunityDTOSchemaFor(urls) snapshots the set once; demand_aggregate needs
membership. OpportunityDTOSchema is built from the built-ins.
- demandAggregatesFromRelease(release, approvedUrls, asOf) rebuilds the DTOs and
throws unless the record's digest, approved-set digest, periods and
membership all hold.
- isPublicCapabilityUrl is documented as a syntax backstop, not a boundary.
The aggregate() fixture moves to a built-in capability, period 2026-08 and past
instants; the opportunity shape fingerprint is re-pinned (releaseDigest).
Agent: implementer-kilo
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FT8WYWkfig4KNcxtMqvMuj
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
…he release contract) Master carries painpoints' #365 (kit-demand.ts: PUBLIC_RELEASE_POLICY, isReleasePeriodClosed and PublicOpportunityRelease), which astra 112c asks demandAggregatesFromRelease to reuse. It also carries 2d80818 (board N64), which registers document-print-and-mail/v1 in loadBuiltinCsds. The one conflict-free consequence is planned: the built-in public set follows the registry, so its pinned list gains that url, in sorted position, here (the test said to do it in the merge commit). Spec 1181/1181 (48 files); package tsc and the contract test file's tsc exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
…e executability, module digests All three open findings were reproduced at a173743 first (scratchpad repro-112c: three failing tests plus a passing control, and a mutation trace for MEDIUM 8). CRITICAL 1 (demand privacy). demandAggregatesFromRelease now enforces #365's exact release contract, reused from types/kit-demand.ts (on this branch since the master merge): - the record's policy must be PUBLIC_RELEASE_POLICY's k and evidence floor exactly, and every aggregate must be counted at that floor (strict literals; the verdict's k 0 / query record is refused); - the period must be releasable under #365's rule, isReleasePeriodClosed: ended at least the 24-hour grace ago by this clock; - PublicOpportunityReleaseRecord is now #365's PublicOpportunityRelease. A demand_aggregate DTO's asOf must also fall after the period's close plus that grace, since no release can exist earlier. HIGH 4 (executability). evidenceIsExecutable checks the CUMULATIVE program, tier 0 through the target. Each tier gets the refs that support it and whose dependencies are satisfiable there (fixpoint), so it is the best assignment the refs allow. computeCsdEligibility's eligibleTier must then reach the target, with live verifiers. A ref that can contribute at no tier up to the target fails it. Payer approval alone is no longer executable at tier 2 or 3, because its tier 1 set is empty (the verdict's reproduction is a test). Positive tier 1-3 cases mark the stub ident.registered_key live inside a try/finally, because no tier 1-3 set is executable with today's registry. MEDIUM 8 (versioning). The lock table also pins the digest of each literal's own module, so any edit there fails CI until it is re-pinned. That includes a refinement whose effect lies outside the finite corpus (the verdict's reproduction now fails on exactly that pin). The Versioning docs list the inputs that live outside each module. Corpus: the payer-only accept cases become honest non-executable cases. New rejects cover payer-only executable claims and reads inside the grace; the demand accept cases move past the grace. The generator is deterministic: the binding and kit corpora came out byte-identical. Tests: spec 1185/1185 (48 files); package tsc and the contract test-file tsc exit 0. Mutations: 8 of 8 killed (grace check, policy, counted evidence, DTO grace, single-tier executability, non-contributing ref, dependency fixpoint, and the out-of-corpus refinement). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
…irement CRITICAL 1 (reproduced at cbc1c14): demandAggregatesFromRelease accepted a self-consistent release whose aggregates were in reverse code-unit order, a record #365's buildPublicRelease can never write. The reader now requires capabilityType strictly increasing in code-unit order (the order #365 sorts into), so the order carries no information. HIGH 4 (reproduced): the dependency pass silently removed a named primitive and then judged the reduced program, so confirm.execution_mode alone was executable at tier 0, and registered_key + execution_mode (no receipt) at tier 1 with the key live. Every named ref must now sit in some tier; otherwise the requirement is not executable. That rule subsumes the old "supports some tier" pre-check, which is removed. The tier-1 expectation that pinned the hole is now false. LOW: the header names every external input (csd/schema.ts, capability-kit.ts). Corpus: 3 new cases (97). LOCK re-pinned: opportunity corpus 4fb26628cccab46e, source 4deb00fd1332e7d0; shape unchanged. Spec 1187/1187, both tsc 0, mutations 11/11 killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
feat(gateway): server-side unmet-demand capture, flag default OFF (R44 D2, stacked on #365)
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.
Summary
Adds the kit-demand signal and the pure layer a publisher would use to release public opportunity aggregates. The signal is the private demand input that Capability Kit work uses to decide which kits to fund.
This is interface and library code only: no routes, no gateway changes, and nothing public is emitted.
@pcc/specUnmetReason,UnmetCapability, and a server-ownedDemandEnvelope.unmet.UnmetCapabilitySchematies the key form to the reason:no_capability_typeonly as a kebab slug, every other reason only as a CSD URI.CallerDemandEnvelopeSchemais the caller input without any server-only field.ServerCapturedDemandEnvelopeSchemais the only schema that carriesunmet.stripServerOnlyDemandFields()stays as a second guard.KitDemandSignalis a private record per capability key. It holds counts by evidence class, exact verified-requester counts and histograms, with consistency refinements and asha256:<hex>digest.PUBLIC_RELEASE_POLICY: k = 5 distinct verified requesters at or above an authenticated-order floor, with a 24 h grace. No caller can choose a policy;isPublishableCapabilityId: a CSD URI whose slug has at most 40 characters, at most 3 hyphens and no run of 4+ digits;periodlabel, never an activity date. Counts are banded 5-9 / 10-24 / 25-99 / 100+;buildPublicRelease()returns one deterministic release per period, with the approved set's digest and a release digest, for a publisher to record once.@pcc/demand-intel:UnmetDemandLensfolds server-captured unmet intents into validated signals.computeForPeriod()produces the input for a release.intent.external_ingest.actorType: "authenticated_operator"rows. Counting is exact, and timestamps come from the server row.Not in this PR (by design)
unmetat capture, actor labels, ingest parsing) is feat(gateway): server-side unmet-demand capture, flag default OFF (R44 D2, stacked on #365) #387, stacked on this PR.Tests (base
ac86a404)@pcc/spec@pcc/demand-intelThe adversarial cases the round-1 review asked for:
kvalues;no_capability_typekey;Also covered: open and in-grace months, signals computed before the close, exact approved-set membership, ID hygiene, release determinism and input immutability.
Each guard was mutation-checked locally: removing it fails the suite. CI build-and-test passes at
6377dd5d.Review
🤖 Generated with Claude Code