Skip to content

integration/consolidated-current — single-branch consolidation + Phase 2 (2A/2B) - #8

Draft
focusedbrain wants to merge 81 commits into
mainfrom
integration/consolidated-current
Draft

integration/consolidated-current — single-branch consolidation + Phase 2 (2A/2B)#8
focusedbrain wants to merge 81 commits into
mainfrom
integration/consolidated-current

Conversation

@focusedbrain

@focusedbrain focusedbrain commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

WR Code / Public Handshake — Email E2E slice, complete through Phase 5 on the single permanent development branch, now stamped for the final rig build.

Milestone Tag / commit Do-not-regress
Phase 2 phase-2-complete @ a310cb96 201 = 201
Consolidation-inherited remediation b46680a4 35 removed, 0 new
Phase 3 phase-3-complete @ 1c1cb3ef 166 = 166
Addendum 3G (contract v1.1) d8ac21b1 166 = 166
Pre-Phase-4 block 56dbf7c2 166 = 166
Phase 4 phase-4-complete @ 0a7ca3ae 166 = 166
Seal-key-source fix 6c758b80 0 new
Phase 5 phase-5-complete @ f12c6262 166 = 166
Rig stamp build047 21389e8e code unchanged (stamp only)

Rig build state

HOST_HASH = 21389e8ebe3936a573b292ad53c76a7ccd095f1b (short 21389e8), origin tip matches, tree clean, phase-5-complete is an ancestor. Expected [RUNTIME_IDENTITY]: commit == 21389e8e…, buildStamp == build047 (21389e8).

The stamp bump moves three literals together because three consumers read them: the extension outDir, VITE_EXT_BUILD_STAMP, and the Windows output-dir marker parsed by kill-wr-desk.cjs. The Electron stamp derives from the extension outDir by regex and appends the short HEAD at build time.

Slice content

Phases 2–5 delivered the provenance gate and fail-open closure, the unsuppressible rule-8 alert, CPR as a typed analysis input, the resolution infrastructure (hardened client, dual-channel validation, head/envelope/EVP verification, contract v1.1 embedded delegation), the A6 three-field status composition with resolution-bearing offers, and the email→offer path with EVP-first-render and manual entry.

E2E acceptance (a)–(d) met at logic level. UI rendering is unverified by design (no build, no app start in the agent session) — Phase-5 report §6 states exactly what that leaves for the rig. The integration-pending list is carried complete: nothing waived, no substitute trust path.

Details: code/docs/analysis/wr-code-email-e2e/ (reports + committed captures/) and code/docs/spec/.

Notes

  • main remains for author-designated document drops only.
Open in Web Open in Cursor 

Local Dev and others added 30 commits July 24, 2026 13:12
…trix, migration and risk, refactor plan)

Co-authored-by: Cursor <cursoragent@cursor.com>
…y, ingress admission, dead-path removal)

One shared full-claim identity guard (issuer+subject+email+wrdesk id, exact
match) replaces every partial comparison on ingest/ack/return paths; old
comparators deleted. Additive iss columns on coordination_handshake_registry
with lazy backfill (first write wins). New per-relationship ingress admission
filter is the first stage for all inbound deliveries incl. the BEAP inbox -
blocked transmissions die pre-visibility with an audit record. Removes dead
skipConsentForAutomation field, unused verifier, and no-op version step, with
structural-absence tests. Realm-distribution inventory + phase report in
docs/analysis/wr-handshake-gap/phase-1-report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
… frozen core record, key extraction, anti-rollback)

Canonicalization module with domain-separation tags; frozen signed core
record (wr_canonical_v3 envelope) carrying the complete capsule content as
a critical declaration, verified fail-closed on receive on top of legacy
rules; containers with preserve-unknown parsing and criticality refusal
naming the namespace; namespace registry (implemented + reserved-inert).

Dual-format emission: v2 surface stays byte-compatible for old peers; new
receivers verify the envelope and mark evidence wire_format canonical_v3 /
legacy_v2. Core nonce store rejects replayed cores; generic anti-rollback
high-water store lands with documented backup/restore semantics. Key
extraction migration (v73) moves private key material into a dedicated
handshake_key_store (copy-before-null, idempotent); reads overlay the store.

Acceptance tests 1-7 green (replay compat, container semantics, canonical
determinism, nonce replay, key extraction, anti-rollback, do-not-regress at
exact baseline parity). ingress_path log-only guard added. Phase report in
docs/analysis/wr-handshake-gap/phase-2-report.md.
…, ledger freeze

- Profile registry (packages/ingestion-core/profileRegistry.ts): five records
  (pbeap_publisher, private_personal, org_internal, org_cross, legacy_v0) fixing
  signature cardinality, attestation rules, role symmetry, permitted ingress
  paths. resolveProfile is fail-closed [VII.4.2]; no conversion path [VII.4.7].
- verifyCanonicalEnvelope dispatches on the registry: unknown profile/version
  refusal naming the profile, distinct-key signature cardinality [VII.3.2],
  schema-level attestation presence/absence [VII.4.5]. New reason codes
  UNKNOWN_PROFILE / PROFILE_SCHEMA_VIOLATION surfaced in denial audit entries.
- Core store split (v75): wr_handshake_core append-only (UPDATE/DELETE aborted
  by triggers, anti-rollback high-water gated) + wr_handshake_runtime mutable
  slice; legacy writers dual-write through coreStore adapter; legacy_v0
  backfill with null ingress_path, unknown_legacy provenance, empty signature
  list (never fabricated).
- Ledger freeze at v74 via freezeAtVersion option + persisted ledger_meta
  marker (closes the lazy-migration hole); one-time hygiene sweep copies out
  and drops undocumented tables, re-asserts row-level key hygiene; hygiene
  assertion on every ledger open.
- ingress_path registry with initial identifiers (Q4 groundwork; log-only).
- Fix: 64-char-hex seed keys signed with a RANDOM key because
  generateKeyPairSync('ed25519', {seed}) silently ignores the seed; now
  wrapped in PKCS#8 DER (canonicalCore.ts, signatureKeys.ts).
- 30 new acceptance tests (profile dispatch, migration parity, hash
  stability, ledger freeze/sweep); phase report with handshake_type inventory
  (249 occurrences / 78 files) and rollback plan.

Co-authored-by: Cursor <cursoragent@cursor.com>
…gate, handshake_type elimination, silent revocation, edge-agent retirement)

Single formation pipeline (V1): formationPipeline.ts dispatches on the
Phase-3 profile registry; the four dialects are deleted (initiatorPersist,
recipientPersist, inbound auto-insert, edge-agent pairing). handshake_type
branching is eliminated - the admission situation is the profile parameter
same_principal (Q9: internal_device, UI label "Cross-Device"); legacy wire
compat is confined to samePrincipalWire.ts, the frozen db column, and
declared envelope-parse boundaries.

Capture methods + Connect-offer staging (V2, C1-C3): capture-method and
invitation-class registries (scan/assisted_discovery fail-closed stubs,
targeted_bound refusal-only); inbound invitations land in connect-offers.db
(own SQLite file, outside both relationship handles) and only a consent
event forms a record. Failed verification suppresses the offer entirely -
structurally unreachable, no override. 7-day timeout (Q7). Capture
provenance is a signed contract declaration (optirando.decl.capture_provenance)
on new formations; consent records are Hash-Pinned (preview +
bound-definition + contract-state hashes) with tamper invalidation.

Silent revocation (V5): revoke-notify capsule removed - enforcement is
exclusively the Phase-1 ingress admission filter (zombie old-build peers'
sends die pre-visibility with a logged record, verified). Q8: revocation no
longer deletes context blocks/embeddings/audit rows; content deletion is
the separate explicit operator action deleteRevokedRelationshipContent
(handshake.deleteRevokedContent RPC).

Edge-agent fold-in (V8/I3): edge_ingestor dialect retired for new
formations (RETIRED_FORMATION_DIALECTS, fail-closed unknown_profile);
legacy pairings stay readable by the agent dist; lockstep upgrade
documented.

Acceptance tests 1-8 green (phase4OneFormationPipeline,
phase4SilentRevocation, phase4EdgeAgentFoldIn acceptance suites +
structural scans). Wide do-not-regress sweep: failure set byte-identical
to the Phase-3 baseline (0 new, 0 fixed). Sequencing evidence and
deviations in docs/analysis/wr-handshake-gap/phase-4-report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
…tap execution consent, Tier-L evidence chain, capability tokens)

Grant objects (E2-E4, E9) [VII.10.x]: wr_grants (migration v76, vault only)
with delivery/preparation rights and deliberately no execute variant; the
Phase-1 receiver-side ingress filter now consumes grant scopes (off-scope
blocked pre-visibility + logged + revoke offer after repetition); admitted
deliveries carry grant_ref provenance on email and P2P BEAP paths; legacy
relationships lazily backfilled from effective_policy (never a fabricated
consent); limit extensions parse-level critical.

Execution grants deleted + per-tap consent (V4) [VII.10.1, IX.19.2]:
GRANTED_TOOLS and ACTIVE-handshake blanket authorization removed; every
execution requires a fresh, single-use, Intent-Hash-bound human consent tap
(executionConsent.ts, wr_execution_consents); divergence from the presented
preview refuses execution and records a deviation PoAE; fail-closed kill
switch WRDESK_EXECUTION_CONSENT_TAP (never a consent-free path).

Evidence chain (H1-H4) [IX.19.1, X.10.1]: wr_evidence_chain - append-only by
trigger, per-contract monotonic sequence, SHA-256 prev-hash chaining,
explicit genesis at cutover; PoAC/PoAE writers on formation, grant
lifecycle, admission blocks, content deletion, and executions; BER schema
representable now (writers in Phase 6).

Ledger repurposing (Q10): handshake-ledger.db is the Tier-L evidence home
(ledger-native schema, hygiene-allowlisted); header docs state the actual
transitional dual role honestly.

Capability-token schema (T4/Q13) [XII.12.6 annex-number-provisional]:
carriage-only tokens with preserve-unknown-optional parsing (p2p_signal
pattern), optional context_scope/delegation_chain, delegable defaults false,
critical limit extensions.

Hygiene (H5): deleteHandshakeRecord no longer deletes audit rows; audit_log
frozen for mutation via triggers on both handles (INSERT stays open);
retention carve-out excludes wr_evidence_chain and audit_log. Pre-existing
purge losses are unrecoverable and the chain claims no pre-cutover
continuity.

Also fixes a Phase-4 latent bug: the default Connect-offer staging DB now
uses :memory: under vitest so test runs stop staging offers into the
developer-profile connect-offers.db (163 leaked fixture rows purged).

Acceptance tests 1-8 green (phase5GrantsEvidence.acceptance.test.ts et al);
do-not-regress verified against the Phase-4 baseline worktree - identical
pre-existing failure set, zero new regressions. Report:
docs/analysis/wr-handshake-gap/phase-5-report.md

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…sponsibility logging

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…json

Co-authored-by: Cursor <cursoragent@cursor.com>
…; stamp build046

Co-authored-by: Cursor <cursoragent@cursor.com>
…nce import depth

Channel provenance (SPF/DKIM/DMARC) and publisher resolution are mandatory
structural pipeline stages, not an opt-in trigger filter. A message failing
them yields no WR code and no affordance at all, so there is no class of
"WRCode-stamped email" a per-trigger checkbox could select. Disabling the
control left it naming a concept that does not exist.

Nothing ever produced the verdict it read: NormalizedEvent.wrcodeValid had no
writer, EventTagMatcher.evaluate has no production caller, and the router
behind the control is fed inline-chat and OCR text, never mail.

Removes the type, union member, evaluators, schema enum value, vestigial
wrcodeMatch field and the control itself; strips the condition from stored
agent configs at every read boundary. InputCoordinator's default: branch now
fails closed to match EventTagMatcher, which makes that stripping load-bearing
rather than cosmetic.

Also fixes email/providers/zoho.ts, which resolved aiProvenance five levels up
where its three peers in the same directory correctly use six - the cause of
both the blocked session:build and the 77 suite-load failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
Brings main's document commits onto the branch so a fresh build covers the
complete state: WR Handshake Phases 1-5, art50 AI provenance, WR Code email
E2E Phase 1, and main. No code conflicts - main touched no source files.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…II additions)

Additive delta to the v1.0 email E2E order: A-series decisions A1-A5,
Phase 3 additions 3D-3F (CatalogHead, DualAssuranceEnvelope, EVP), Phase 4
offer-schema and status additions, Phase 5 EVP-first-render, and acceptance
items (e)-(h).

Tracked rather than left in chat because the v1.0 order never was, which is
part of how the work lost its paper trail.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…ug 2026)

Normative source for Delta v1.1 decisions A2 (EVP-first-render, XVII.4.4),
A4 (audit link, XVII.6) and A5 (platform suspension, XVII.3.3). Placed at
repository root alongside the other annexes.

Note: this annex depends throughout on Annex XIV 5.5 (Execution Authorization
Proof Chain and Catalog Commitment), which the Annex XIV copy in this repo
(v1.0, 26 July 2026) does not contain.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Author ruling on the collision named against Annex XVII XVII.3.2/XVII.3.3:
three orthogonal layers whose convergence is display-only. entry.status is
publisher-signed and platform suspension lives only in the envelope, so the
two cannot collide in data. Admission is conjunctive and fail-closed across
D4 status, entry.status and envelope.suspension; display keeps all three
distinct with headline precedence platform > entry > publisher-part.

Phase 4 bullet cross-references A6 for the composition rule.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Supersedes the A5 cross-reference wording with the authoritative statement:
the surface composes three orthogonal fields (D4 publisher-part status,
publisher-signed entry.status, platform envelope.suspension) under A6's
conjunctive fail-closed admission rule and headline precedence, with
distinct copy per layer. Offer-schema bullet unchanged.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Brings Refactor Order v1.0 (754e87e), the authoritative Annex XIV v1.1
carrying XIV.5.5, Annex XVII, and the updated Annexes IX/X/XI onto the
Phase-2 branch before order-02 work begins. Plain merge commit per the
author ruling: no cherry-picks, no rebase.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
cursoragent and others added 30 commits August 8, 2026 19:04
…erivation

B. PR #7 replaced the local_role role derivation with the canonical
coordination-device-id derive, on the grounds that local_role is a per-device
view that can disagree with the ledger. These fixtures still set only
local_role and no coordination ids, so the derive returned
device_id_not_in_handshake and the product refused every prepare.

The fail-closed refusal is the enforcement #7 added and is left untouched;
the fixtures now name the local instance, following the 2a24cdb precedent.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
A2. The four failing cases sealed rows with the inner key, tagged them
seal_key_source='vmk', then unbound the inner provider and expected the read to
succeed anyway. sealedQuery routes vmk rows to the inner provider only, so the
rows were filtered and prepare returned MESSAGE_NOT_FOUND. No product change
can make that pass without a verification bypass, and reject mode states there
is none in any environment. The tests encode the pre-B-9 trusted read that
"source read uses sealedQuery" deliberately removed.

Three of them are about content extraction per row shape, not about key
routing, so they now seal per the product's own policy
(effectiveInboxRowSealKeySource returns 'ledger' for every one of these
shapes) and exercise their real subject.

The fourth asserted cloning a row with no canonical plaintext. Production
writes NULL depackaged_json only where no plaintext exists yet
(beap_qbeap_pending_main, main-process decode errors), so there is nothing to
clone; it now pins the refusal rather than a body_text fallback.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…bient mode

A3. CC_06 asserted that use_coordination=false sends outbound to a relay URL.
That path was retired; the queue refuses it with a typed permanent
PREFLIGHT_FAILED / CONFIG_PERMANENT and never calls fetch. The test now pins
the refusal, per the ruling that the refusal is the invariant worth keeping.

A4. CC_05b enqueued a content-bearing message_package and discarded the
result. enqueueOutboundCapsule does return a typed rejection on every path and
logs it, so nothing was dropped silently and no product change is warranted;
the silence was the test throwing the result away. It now asserts the enqueue.

The rejection was SANDBOX_DATA_EGRESS_FORBIDDEN: the Electron mock's userData
dir is shared and persists across files and runs, and roughly seventeen suites
write orchestrator mode into it, so whichever suite ran last decided whether
this one could send. That is the mechanism behind the false green. The suite
now pins host mode for its own duration and restores what it found.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
All 35 pinned identities green in isolation and in the full workspace, 0 new
failures, 201 -> 166. No product code changed: every fix is in tests or the
harness.

Records what the package deliberately did not do. A2 needed no product change:
the four cases asserted the pre-B-9 trusted read that "source read uses
sealedQuery" removed. A4's authorized typed rejection already exists -
enqueueOutboundCapsule returns a typed result on every path and logs it, so the
never-fails-silently condition never triggered; the silence was the test
discarding it.

Names the root cause behind the false green: the Electron mock shares one
userData dir whose orchestrator-mode.json survives across files and runs, and
two suites write mode 'sandbox' without restoring it. Correcting the count in
c5447a4, which said roughly seventeen; it is two.

Adds the error-taxonomy item for the MESSAGE_NOT_FOUND conflation with a
remediation proposal, and the seal-policy divergence between
verificationKeySourcesForInboxRow and sealedQuery. Both reported, neither
implemented.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Remediation accepted; the consolidation-inherited item is closed. What came
out of it now carries its own names and schedules, so none of it rides along
unowned:

- test-isolation bidirectional risk: root cause attached, mock untouched until
  a dedicated decision.
- inbox-read error taxonomy: adopted in principle, scheduled with Phase 4 as
  an authorized bounded item - same never-fails-silently family as the Phase-4
  status surface.
- seal-key-source policy unification: bounded diagnosis at Phase-4 entry,
  implementation only on approval.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Interface reference against WRC-Registry-API-Contract v1.0 @20794bff. No WRC
service code; everything below the transport interface is swappable and an
unconfigured deployment refuses with not_configured rather than looking like a
registry that answered "no such publisher".

3A hardened client: https only, redirect refused rather than followed, TLS 1.2
floor with no way to weaken rejectUnauthorized, total deadline, streaming size
cap, and an SSRF guard at the resolved ADDRESS via a custom lookup - checking
the hostname would fall to DNS rebinding. Placed in main rather than a shared
package because both shared packages are imported by the MV3 extension and the
guards need node:dns/net/https; reported as a deliberate deviation.

3B orders the chain so the ordering is the security property: registry claim,
then DNS + manifest, then the part cross-check, then head, envelope, EVP. No
branch reaches a trusted presentation having skipped a leg, and the registry is
consulted last for key material so it can only ever be caught diverging.

3C folds r7 alignment through the CPR ratchet, so this stage can tighten a
verdict and structurally cannot loosen one, and activates discovery_record.

3D persists the epoch floor separately from the record cache: evicting a
publisher must not reopen a rollback window.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…ment

Contract-faithful double signs real Ed25519 material, builds a real Merkle
tree and a real epoch sequence, so verification is proven against signatures
rather than stubs that return true.

Two findings from writing them, both fixed here:

- parseOutboundUrl let a bracketed IPv6 literal through. URL.hostname keeps the
  brackets and isIP('[::1]') is 0, so https://[::1]/ passed the literal-address
  check and was caught only later by the lookup guard. Now unwrapped.

- The signature tamper helper flipped the LAST base64url character, which for a
  64-byte signature carries two meaningful bits and four discarded padding
  bits. Several tampers decoded to identical bytes, so whether a test caught a
  forged signature depended on which random key was generated. Tampering the
  first character now, with an assertion that the decoded bytes actually
  differ; the matrix was run five times over fresh keys to confirm.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Records the deliberate 3A placement deviation and its reason, the two findings
from writing the tests (bracketed IPv6 bypassing the URL guard; a signature
tamper that decoded to identical bytes and made forgery detection depend on the
random key), the integration-pending live legs, and one contract gap: the
contract defines DelegationRecord and a write endpoint for it but no read path,
so a client that has never seen a delegation cannot verify a head signed by a
delegated catalog key.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Amendment as an additive delta beside the byte-exact v1.0 author drop, CRLF
preserved, following the Order v1.0 + Delta v1.1 precedent in this repo.

The head already arrives on every resolve, so embedding the delegation makes
verification deterministic and immune to selective blocking of a side-fetch.
resolveSigningKey therefore takes a single headDelegation rather than a list:
a collection-shaped field is an invitation to satisfy a delegated head from
somewhere other than the head, which is the property the amendment buys.

Each way of getting it wrong is its own reason - missing record, kid mismatch,
not rooted, out of window, bad signature - because a status surface that
collapses them cannot tell an operator what happened. Sub-delegation is
unrepresentable rather than refused: authority is catalog-signing-only, so a
record naming anything but the DNS-pinned root as root_kid is rejected before
its signature is considered.

A malformed embedded record fails the decode instead of degrading to null,
which would have turned a broken chain into "root-signed head" and handed the
verifier the wrong question.

The delegations endpoint is audit-only and guarded as such: source-walking
asserts the verification modules never reference it, and a deliberately broken
audit endpoint leaves verification green.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Addendum records the v1.1 amendment, the client change and its rationale, the
eight negative cases with their distinct reasons, and the clean do-not-regress
pair (166=166, +16 passing).

Codifies the two rulings this phase produced: node-only guards never live in
browser-reachable packages, and every negative-test mutation helper must assert
its own semantic effect - a helper whose effect depends on fixture randomness
is invalid by construction.

Closes the contract gap as ruled, and schedules epoch-floor hardening into the
pre-Phase-4 block with the two existing items; bound-origin-set plurality goes
with 5A instead.

Narrowing worth noting: delegation fetch is no longer integration-pending for
verification, because verification never fetches. Only the live audit
round-trip against GET /delegations remains.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
MESSAGE_NOT_FOUND covered three states - row absent, row present but
unverifiable, row present with no canonical plaintext - so neither an operator
nor a log could tell a missing message from a tampered one from a locked vault.
The Phase-2 diagnosis needed a probe to find out which had happened.

MESSAGE_NOT_FOUND now means the row is genuinely absent, and its copy drops the
"or could not be verified" hedge. SOURCE_UNVERIFIABLE and
SOURCE_NO_CANONICAL_CONTENT carry the other two, following the pattern the
vault-unavailability branches already use.

Absence of content outranks unverifiability: you cannot verify what is not
there, and "no decrypted content yet" is the actionable thing to say. The
classification reads the canonical column without returning it - presence only,
never content, since this sits behind the gate it is classifying.

Renderer copy and the prepare-failure detail list updated; b9 assertions split
by the state each case actually creates.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
A floor a deletable userData file can reset is decorative: anyone able to
remove the file could let a publisher replay an older, correctly signed
CatalogHead. Schema v77 adds wrc_publisher_epoch_floor; the resolved-record
store keeps only cache and no longer owns the floor.

Monotonicity is a property of the statement rather than of a read-then-write a
caller could race or skip, and the store exposes exactly two operations, read
and raise - there is no set, clear, or delete to call. A legacy cache file's
epoch_floor key is ignored on load, since reading it back would reintroduce the
reset path this removes.

When the DB is unavailable the fallback is an in-process floor. That is
strictly safer than what it replaces: empty for the process, but unlowerable
and never written anywhere a deletion could reach.

Guard test deletes the cache and serves an older correctly signed head; it
still fails with head_epoch_rollback.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Six production call sites; one uses the policy. Sites 2-4 are the extension's
sealed inbox read, which routes from the row tag alone.

Consequence: a legacy inner-sealed non-confidential row is visible in the
Electron inbox - which tries the policy list, succeeds on inner, and re-seals
forward - and absent from the extension inbox, which maps vmk to inner only and
filters the row while emitting tamper telemetry for a row that is not tampered.
Wider than the Phase-3 report framed it, which named clone-prepare rather than
the extension list.

Authoritative: the policy decides which providers may be TRIED; the row tag
records what the row WAS sealed with. The tag cannot govern policy because it
is exactly the field that is stale on the legacy rows the try-list exists for.

Recommends teaching sealedQuery an optional key-source list, keeping today's
default, then migrating the extension list first. No code changed; the fix is
gated on approval.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
The Phase-3 assertion proved the floor outlived record eviction by reading it
back out of the same cache persistence - which is precisely the property item
(iii) removed. Sharing the floor store between the two record stores asserts
the new reason: the floor is its own store, so neither eviction nor deleting
the cache file reopens a rollback window.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Records (i) and (iii) as implemented, (ii) as diagnosis with the fix gated on
approval, and the clean do-not-regress pair.

Also records the one correction the identity comparison caught: a Phase-3 test
proved the floor survived eviction by reading it back out of the cache
persistence, which is the property (iii) removes. A count-only check would have
shown 167 vs 166 and invited a shrug.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
4A composes three orthogonal layers instead of merging them. The two
"suspended" statements are different parties talking about different objects -
the publisher withdrawing its entry, and the platform suspending the object -
so they get distinct copy and never collapse into one word on one screen.
Admission is conjunctive and fail-closed; the headline is the failing leg
closest to the object; every failing leg stays visible, because
never-fails-silently means telling the operator all of what is wrong rather
than the first thing. Compromised is revoked plus the unsuppressible alert
flag, and superseded surfaces its successor without offering it.

4B carries resolution output on the offer - all of it from the verified chain,
none from carrier bytes - and extends the preview hash to cover the entry, the
resolution mode, and per the O2 extension the evp_ref and value_statement. That
last one is the substance: consent is pinned to the hash, so if two offers
showing different signed value promises hashed identically, the consent record
would not bind what the operator actually agreed to.

O6 re-checks status at consent time, since a publisher can withdraw inside the
window between staging and consent. The 7-day offer timeout stays UI staleness
and is not this gate.

CREATE TABLE IF NOT EXISTS does nothing to an existing database, so the new
columns are added explicitly and idempotently.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
The Phase-4 acceptance guard pins every reader of the staged-offer table to
connectOfferStaging, so no second module can build an alternate, unsuppressed
listing. I had put the O6 gate in ipc.ts, which violated exactly that. The
guard was right; the gate moved to the module that owns the table.

Worth noting the comment I first wrote to explain the move named the table
literally, which tripped the same scanner - the invariant is enforced by
substring, so prose about it has to avoid the string.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Records the A6 composition and why the three layers stay separate, the
preview-hash coverage extension and why it is the substance of 4B rather than
paperwork, the O6 gate, and the clean do-not-regress pair.

Also records that an existing acceptance guard caught a real placement error:
the O6 gate belonged in the module that owns the staged-offer table, not in
ipc.ts. The guard was right.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…just the tag

The extension's sealed inbox read routed from seal_key_source alone. A legacy
inner-sealed NON-confidential row was therefore filtered whenever the inner
vault was locked - invisible in the extension while the Electron inbox showed
it - and recorded as a tamper event although nothing about the row was
tampered. False tamper telemetry about an untampered row is the part that made
this a correctness bug rather than a visibility quirk.

sealedQuery gains an OPTIONAL per-row key-source resolver. A resolver rather
than a flat list because policy is per row: a batch mixes confidential rows
(inner only) with non-confidential ones (outer, then inner), and one union list
for the whole batch would let a confidential row verify against the outer key -
the opposite of what the option is for.

Tamper is recorded only when EVERY permitted provider fails. A first-candidate
miss is not evidence about the row.

Absent the option nothing changes: same single-source derivation, same
branches, same telemetry. The three extension read sites opt in; the others are
untouched in this change.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Records the approved scope a-d, including two things that differ from the
recommendation as written.

The option is a per-row resolver rather than a flat list: a batch mixes
confidential rows with non-confidential ones, and one union list for the batch
would let a confidential row verify against the outer key - the opposite of
what the option is for.

The ruling said five remaining call sites; there are six in total and three
were migrated, so three remain, one of which already used the policy. Flagged
rather than quietly reporting a different number.

Data migration is not required: nothing stored changes, only which providers
are tried at read time. But the extension path is read-only, so unlike
inboxSealedRead it does not reseal legacy rows forward - the drift is tolerated
rather than healed by extension use.

Two identities went green across the change and are NOT claimed as its effect:
diagnostics.test.ts fails 7 of 19 in isolation every run while its
full-workspace count moves with scheduling. Recorded as a third instance of the
named test-isolation risk, along with the VM reprovision that made a fresh
baseline at the same commit read 168 where Aug-9 read 166.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…eseal drift

Backfills the seal-key-source pair's identity sets and codifies the rule that
produced them: the artifact that makes identity comparison possible has to
survive the VM. Identity lists rather than the 2 MB JSON, since the strings and
the guard counts are the only load-bearing parts.

The README states the two traps this repo has already hit: counts alone are not
the criterion when suites change their full-run count with scheduling, and a
pair is only meaningful within one environment.

Also records "legacy reseal drift" as a low-priority named note - the read-only
extension path never reseals a legacy row forward, so the tag clears only via
the Electron detail path.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…ual entry

5A. ingressMappingForSource no longer defaults unknown transport sources to
beap_invitation/assisted_email. That default was tolerable only while
assisted_email had no live producer; this phase gives it one, so a silent
default would attest that the user received something by assisted email when
nobody knows how they received it. Totality is preserved - the acceptance test
still holds - but the pair is honest and deliberately matches no map entry, so
consent fails rather than recording a capture nobody performed. WR-code sources
get their own registered ingress paths.

startBeapEmailSync is retired in place with the reason recorded: it has had no
caller for the whole slice, and a second ungated ingest path would bypass the
CPR gate Phase 2 closed. Not deleted, because the module still exports a live
function and that is a wider change than this phase.

5B. Offer presentation is a projection with the rule inside it, for the same
reason the rule-8 alert works that way: a per-surface predicate is how "never
show carrier text" becomes "usually does not". No verified EVP means refusal,
not an offer assembled from something else - there is no branch that builds a
partial one. The local renderer regenerates grouping from the validated
identifier, so there is no path from carrier bytes to a rendered code.

5C. Manual entry works precisely because the capture gate never consults
provenance; that is what makes it the one downgrade path for forwarded mail.

Delta. Consent-time CatalogHead recheck distinguishes rollback from staleness
from a normal new epoch, since only the first is an attack shape and only the
last should send the operator back to a re-staged offer.

Extension CPR plumbing closed: depackaged_metadata now crosses the sealed
inbox RPC and is decoded by the same fail-closed extractor the Electron
surfaces use, so the extension alert renders from one rule over one projection.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
The ingress fail-closed guard used wr_code_public as its unmapped fixture.
Phase 1 recorded that path as registered-but-unmapped and predicted Phase 5
would have to map it once the email→offer path went live; 5A did exactly that,
so the fixture moved to relay_code_claim, which is still registered and still
unmapped. The guard is about the fail-closed rule, not about which path happens
to lack a mapping today - weakening it to accommodate the new mapping would
have inverted its purpose.

The Phase-2 panel test asserted the alert reads the prop alone, which was the
correct contract while the surface had no data and was documented as unable to
alert. Plumbing closed that; the prop stays as the explicit override.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…ending

Records the ingressMappingForSource ruling and why the old default became a
fabrication once assisted_email got a live producer; beapSync retired in place
with the reason; the offer projection and why refusal rather than a partial
offer is what "no degraded offer" has to mean; and the four-way consent-time
head recheck.

Section 6 states plainly what no-build/no-app-start leaves unverified: the
projection and its rules are tested, no pixel is. The preview-hash input is
covered but the wiring from a rendered surface into consent is UI work for the
rig.

The integration-pending list is carried complete, with one narrowing already
noted: delegation verification never fetches, so only the audit round-trip
remains.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Bumped as part of a commit, never hand-edited at build time, so the stamp the
running app reports is derivable from the tree that produced it. Three literals
move together because three consumers read them: the extension outDir, the
extension's VITE_EXT_BUILD_STAMP, and the Windows output-dir marker that
kill-wr-desk.cjs parses. The Electron stamp is derived from the extension
outDir by regex and gets the short HEAD appended at build time, so it needs no
edit of its own.

Slice content is unchanged from phase-5-complete; this commit is the stamp only.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Read-and-report pass before the rig build. A-E evidence with paths and command
output; nothing fixed.

One finding: a new TS2322 in BeapMessageDetailPanel, established by diffing the
extension typecheck at HEAD against the pre-Phase-5 commit rather than
asserting it. Phase 5 typed BeapMessage.channelProvenance with verdict: string
where the component requires the ChannelAlertVerdict union. Runtime is
unaffected and vite build does not typecheck, so the rig build would likely
succeed anyway - but it is a real defect in the exact plumbing named as the top
rig risk, and it is one line.

Producer/consumer RPC shapes were diffed mechanically and agree 19/19 on both
handlers. The workspace TS2307 class is pre-existing: ingestion-core points at
dist/ and dist does not exist until that package is built.

The full-workspace capture identity-compared against the COMMITTED phase-5
after-set: 166 = 166, 0 new, 0 gone. First use of the capture-persistence rule
against an artifact rather than a /tmp file.

Graphify was lost with the reprovisioned VM, so navigation was by source; the
graph was never admissible as evidence anyway.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
The field was declared with verdict: string, widening it away from the union
the shared component requires. It still ran correctly - the only producer emits
union members or null - but the type no longer proved that, and vite build does
not typecheck, so the mismatch would have reached the rig invisibly.

Now imports ChannelProvenanceAlertRecord from @repo/shared-beap-ui rather than
redeclaring it; the component's prop is untouched and stays narrow.

Two assertions keep it from re-widening: a compile-time one assigning the
producer's output to ChannelProvenanceAlertProps['record'], which fails tsc if
the types drift, and a source check that the field is the imported type and not
a structural copy.

Extension typecheck vs the pre-Phase-5 commit cd282ea: 183 == 183, sets
identical, zero new.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
…nd spot

Records both gates re-run after the authorised fix: extension typecheck
183 == 183 with identical sets against cd282ea, and the sanctioned capture
identity-comparing 166 = 166 with zero new against the committed phase-5
after-set. numTotalTests moved 6115 -> 6117 for the two new assertions; the
failure identity set did not move.

Adds the named item "typecheck blind spot" with its diagnosis scope for after
the rig pass. It is the same shape as the defect this pass caught - a green
signal that was not measuring what it appeared to measure - which is why the
masked-errors question is the part worth answering, not the resolution setup.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
Supersedes build047. That stamp was minted at 21389e8, and 183e147 then
changed extension code (the channelProvenance narrowing) on top of it - so
build047 no longer identifies exactly one tree. Reusing it would let two
different trees report the same stamp, which is the ambiguity the
"bump as part of a commit, never hand-edited" rule exists to prevent.

Same three literals move together: extension outDir, VITE_EXT_BUILD_STAMP, and
the Windows output-dir marker parsed by kill-wr-desk.cjs.

Co-authored-by: focusedbrain <focusedbrain@users.noreply.github.com>
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