From 7b2f2aa71cbbc6e2820c3b870f9f6fe8ca6ef172 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 00:00:38 -0700 Subject: [PATCH 1/7] feat: add continuum evidence posture --- docs/BEARING.md | 9 +- .../v18-evidence-posture.md | 127 ++++++++++++++++++ .../v18-evidence-posture.md | 54 ++++++++ index.ts | 8 ++ .../continuum/ContinuumEvidencePosture.ts | 50 +++++++ .../continuum/ContinuumEvidenceStatus.ts | 91 +++++++++++++ .../continuum/ContinuumEvidenceStatus.test.ts | 63 +++++++++ test/unit/domain/index.exports.test.ts | 14 ++ 8 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 docs/design/0150-v18-evidence-posture/v18-evidence-posture.md create mode 100644 docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md create mode 100644 src/domain/continuum/ContinuumEvidencePosture.ts create mode 100644 src/domain/continuum/ContinuumEvidenceStatus.ts create mode 100644 test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts diff --git a/docs/BEARING.md b/docs/BEARING.md index cd51494d..c964020a 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -178,8 +178,13 @@ before the final commit for that slice, and mark completed items with `- [x]`. and one-file-per-concept caps, self-attested authority fields from artifact JSON are rejected, policy-test authority fixtures are named constants, and empty or internally inconsistent Wesley generated inventory is rejected. -- [ ] 6. Make evidence posture explicit: translated git-warp evidence first, - native Continuum evidence only after native witnesshood is proven. +- [x] 6. Make evidence posture explicit: translated git-warp evidence first, + native Continuum evidence only after native witnesshood is proven. The + current seam adds runtime-backed `ContinuumEvidencePosture` and + `ContinuumEvidenceStatus`; translated git-warp evidence is explicit + `translated-substrate` evidence, native Continuum evidence cannot be + constructed without `nativeWitnessRef`, and translated evidence rejects native + witness references. - [ ] 7. Prove the patch commit visibility contract: success means canonical writer-tip advancement and visible graph truth, not just object creation. - [ ] 8. Add the same-writer concurrent patch race witness with final-frontier diff --git a/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md b/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md new file mode 100644 index 00000000..03438fa0 --- /dev/null +++ b/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md @@ -0,0 +1,127 @@ +--- +cycle: 0150 +task_id: V18_evidence_posture +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Evidence Posture + +## Pull + +The generated-artifact seam can now admit Continuum-family descriptors, but +the next compatibility cut must prevent a shaped value from pretending to be a +native Continuum witness. + +## Hill + +`git-warp` has a runtime-backed evidence status that separates translated +substrate evidence from native Continuum evidence and requires a native witness +reference before native evidence can be claimed. + +## Playback Questions + +Agent: + +- Does translated git-warp evidence carry an explicit translated posture? +- Does native Continuum evidence require a native witness reference? +- Does the model reject translated evidence that tries to smuggle in a native + witness reference? + +Human: + +- Can later v18 receipt projections say "this is git-warp evidence translated + into a Continuum-family shape" without overclaiming? + +## Accessibility / Assistive Reading Posture + +The evidence status is plain data with stable string fields. No visual-only +state is introduced. + +## Localization / Directionality Posture + +The posture values are protocol identifiers and not localized prose. Human +summaries remain ordinary strings supplied by callers. + +## Agent Inspectability / Explainability Posture + +The status object exposes posture, source runtime, basis reference, optional +native witness reference, and summary as inspectable fields. + +## Non-Goals + +- Do not implement native Continuum witness production. +- Do not generate receipt-family values in this slice. +- Do not build a generic WARP Optic engine. + +## RED + +Expected failing spec: + +```text +npx vitest run test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts +``` + +Observed RED: + +```text +Error: Cannot find module '../../../../src/domain/continuum/ContinuumEvidencePosture.ts' +``` + +## GREEN + +This slice adds: + +- `ContinuumEvidencePosture` +- `ContinuumEvidenceStatus` + +Translated git-warp evidence is represented as `translated-substrate` with +`sourceRuntime: "git-warp"`. Native Continuum evidence is represented as +`continuum-native` and cannot be constructed unless `nativeWitnessRef` is +present. Translated evidence rejects `nativeWitnessRef` so compatibility output +cannot smuggle native witnesshood through an optional field. + +## Playback + +Witness: + +```text +npx vitest run test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts test/unit/domain/index.exports.test.ts +Test Files 2 passed (2) +Tests 55 passed (55) + +npm run typecheck:src -- --pretty false +``` + +Agent answers: + +- Yes, translated git-warp evidence carries explicit translated posture. +- Yes, native Continuum evidence requires `nativeWitnessRef`. +- Yes, translated evidence with `nativeWitnessRef` is rejected. + +Human answer: + +- Later receipt-family projections can carry translated git-warp evidence + without claiming native Continuum witnesshood. + +## SSJS Scorecard + +- Runtime-backed forms: green; both new concepts are classes with constructor + validation and frozen instances. +- Boundary validation: green; no raw boundary parsing was introduced. +- Behavior ownership: green; posture validation and evidence-status invariants + live on the evidence concepts. +- Message parsing: green; no message parsing introduced. +- Ambient time or entropy: green; no ambient time or entropy introduced. +- Fake shape trust or cast-cosplay: green; native evidence cannot be claimed + without an explicit native witness reference. + +## Closeout + +This closes BEARING task 6 and gives receipt-family projection work an honest +evidence-status carrier. diff --git a/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md b/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md new file mode 100644 index 00000000..2a620907 --- /dev/null +++ b/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md @@ -0,0 +1,54 @@ +--- +cycle: 0150 +task_id: V18_evidence_posture +status: Complete +sponsors: + human: James + agent: Codex +completed_at: 2026-05-21 +--- + +# Retro: V18 Evidence Posture + +## Hill + +`git-warp` has a runtime-backed evidence status that separates translated +substrate evidence from native Continuum evidence and requires a native witness +reference before native evidence can be claimed. + +## Result + +Hill met. + +## Witness + +```text +npx vitest run test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts test/unit/domain/index.exports.test.ts +Test Files 2 passed (2) +Tests 55 passed (55) + +npm run typecheck:src -- --pretty false +``` + +## Drift Check + +No drift. The implementation stayed within the evidence-posture slice and did +not start receipt-family projection or native witness production. + +## What Mess We Got Into + +The repo had a generated-artifact gate but no runtime object for the more +dangerous claim: whether a Continuum-shaped value is native evidence or merely +translated substrate evidence. + +## What Mess We Got Out Of + +Native Continuum evidence now has to carry `nativeWitnessRef`. Translated +git-warp evidence is the explicit default and cannot include that native +witness field. + +## What Comes Next + +Prove that patch commit success means canonical writer-tip advancement and +visible graph truth, then use that proven source fact for receipt-family +projection. diff --git a/index.ts b/index.ts index 260c1a82..245ef7e5 100644 --- a/index.ts +++ b/index.ts @@ -211,10 +211,14 @@ import { import ContinuumArtifactAuthority from './src/domain/continuum/ContinuumArtifactAuthority.ts'; import ContinuumArtifactDescriptor from './src/domain/continuum/ContinuumArtifactDescriptor.ts'; import ContinuumArtifactIngestionPolicy from './src/domain/continuum/ContinuumArtifactIngestionPolicy.ts'; +import ContinuumEvidencePosture from './src/domain/continuum/ContinuumEvidencePosture.ts'; +import ContinuumEvidenceStatus from './src/domain/continuum/ContinuumEvidenceStatus.ts'; import ContinuumFamilyId from './src/domain/continuum/ContinuumFamilyId.ts'; import ContinuumArtifactJsonFileAdapter from './src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; import type { ContinuumArtifactAuthorityValue } from './src/domain/continuum/ContinuumArtifactAuthority.ts'; import type { ContinuumArtifactDescriptorFields } from './src/domain/continuum/ContinuumArtifactDescriptor.ts'; +import type { ContinuumEvidencePostureValue } from './src/domain/continuum/ContinuumEvidencePosture.ts'; +import type { ContinuumEvidenceStatusFields } from './src/domain/continuum/ContinuumEvidenceStatus.ts'; import type { ContinuumFamilyIdValue } from './src/domain/continuum/ContinuumFamilyId.ts'; import type { ContinuumArtifactJsonLoadContext } from './src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; @@ -333,6 +337,8 @@ export { ContinuumArtifactAuthority, ContinuumArtifactDescriptor, ContinuumArtifactIngestionPolicy, + ContinuumEvidencePosture, + ContinuumEvidenceStatus, ContinuumFamilyId, ContinuumArtifactJsonFileAdapter, @@ -384,6 +390,8 @@ export type { SyncRateLimitConfig, ContinuumArtifactAuthorityValue, ContinuumArtifactDescriptorFields, + ContinuumEvidencePostureValue, + ContinuumEvidenceStatusFields, ContinuumArtifactJsonLoadContext, ContinuumFamilyIdValue, }; diff --git a/src/domain/continuum/ContinuumEvidencePosture.ts b/src/domain/continuum/ContinuumEvidencePosture.ts new file mode 100644 index 00000000..2c433f34 --- /dev/null +++ b/src/domain/continuum/ContinuumEvidencePosture.ts @@ -0,0 +1,50 @@ +import WarpError from '../errors/WarpError.ts'; + +const TRANSLATED_SUBSTRATE_POSTURE = 'translated-substrate'; +const CONTINUUM_NATIVE_POSTURE = 'continuum-native'; + +export type ContinuumEvidencePostureValue = + | typeof TRANSLATED_SUBSTRATE_POSTURE + | typeof CONTINUUM_NATIVE_POSTURE; + +export const CONTINUUM_EVIDENCE_POSTURES: readonly ContinuumEvidencePostureValue[] = Object.freeze([ + TRANSLATED_SUBSTRATE_POSTURE, + CONTINUUM_NATIVE_POSTURE, +]); + +/** Runtime-backed evidence posture for Continuum-compatible values. */ +export default class ContinuumEvidencePosture { + readonly value: ContinuumEvidencePostureValue; + + constructor(value: string) { + this.value = requireContinuumEvidencePosture(value); + Object.freeze(this); + } + + /** Returns true for compatibility evidence translated from git-warp substrate facts. */ + isTranslatedSubstrate(): boolean { + return this.value === TRANSLATED_SUBSTRATE_POSTURE; + } + + /** Returns true only for values backed by native Continuum witnesshood. */ + isContinuumNative(): boolean { + return this.value === CONTINUUM_NATIVE_POSTURE; + } + + /** Returns the stable posture string. */ + toString(): string { + return this.value; + } +} + +/** Validates a raw evidence posture string. */ +export function requireContinuumEvidencePosture(value: string): ContinuumEvidencePostureValue { + const valid = CONTINUUM_EVIDENCE_POSTURES.find((candidate) => candidate === value); + if (valid === undefined) { + throw new WarpError( + `Continuum evidence posture must be one of: ${CONTINUUM_EVIDENCE_POSTURES.join(', ')}`, + 'E_VALIDATION', + ); + } + return valid; +} diff --git a/src/domain/continuum/ContinuumEvidenceStatus.ts b/src/domain/continuum/ContinuumEvidenceStatus.ts new file mode 100644 index 00000000..5999b563 --- /dev/null +++ b/src/domain/continuum/ContinuumEvidenceStatus.ts @@ -0,0 +1,91 @@ +import WarpError from '../errors/WarpError.ts'; +import ContinuumEvidencePosture from './ContinuumEvidencePosture.ts'; + +export type ContinuumEvidenceStatusFields = { + readonly posture: string | ContinuumEvidencePosture; + readonly sourceRuntime: string; + readonly basisRef: string; + readonly summary: string; + readonly nativeWitnessRef?: string; +}; + +export type TranslatedGitWarpEvidenceFields = { + readonly basisRef: string; + readonly summary: string; +}; + +/** Runtime-backed evidence status for Continuum-compatible projections. */ +export default class ContinuumEvidenceStatus { + readonly posture: ContinuumEvidencePosture; + readonly sourceRuntime: string; + readonly basisRef: string; + readonly summary: string; + readonly nativeWitnessRef: string | undefined; + + constructor(fields: ContinuumEvidenceStatusFields) { + this.posture = normalizePosture(fields.posture); + this.sourceRuntime = requireNonEmptyString(fields.sourceRuntime, 'sourceRuntime'); + this.basisRef = requireNonEmptyString(fields.basisRef, 'basisRef'); + this.summary = requireNonEmptyString(fields.summary, 'summary'); + this.nativeWitnessRef = optionalNonEmptyString(fields.nativeWitnessRef, 'nativeWitnessRef'); + validateNativeWitnessPosture(this.posture, this.nativeWitnessRef); + Object.freeze(this); + } + + /** Creates the default v18 evidence posture for git-warp compatibility output. */ + static translatedGitWarp(fields: TranslatedGitWarpEvidenceFields): ContinuumEvidenceStatus { + return new ContinuumEvidenceStatus({ + posture: 'translated-substrate', + sourceRuntime: 'git-warp', + basisRef: fields.basisRef, + summary: fields.summary, + }); + } + + /** Returns true for compatibility evidence translated from substrate facts. */ + isTranslatedSubstrate(): boolean { + return this.posture.isTranslatedSubstrate(); + } + + /** Returns true only when native Continuum witnesshood is explicitly carried. */ + isContinuumNative(): boolean { + return this.posture.isContinuumNative(); + } +} + +/** Normalizes a posture carrier. */ +function normalizePosture(value: string | ContinuumEvidencePosture): ContinuumEvidencePosture { + if (value instanceof ContinuumEvidencePosture) { + return value; + } + return new ContinuumEvidencePosture(value); +} + +/** Validates a required non-empty string. */ +function requireNonEmptyString(value: string, name: string): string { + if (typeof value !== 'string' || value.length === 0) { + throw new WarpError(`${name} must be a non-empty string`, 'E_VALIDATION'); + } + return value; +} + +/** Validates an optional non-empty string. */ +function optionalNonEmptyString(value: string | undefined, name: string): string | undefined { + if (value === undefined) { + return undefined; + } + return requireNonEmptyString(value, name); +} + +/** Enforces that native evidence cannot be claimed by posture alone. */ +function validateNativeWitnessPosture( + posture: ContinuumEvidencePosture, + nativeWitnessRef: string | undefined, +): void { + if (posture.isContinuumNative() && nativeWitnessRef === undefined) { + throw new WarpError('nativeWitnessRef is required for native Continuum evidence', 'E_VALIDATION'); + } + if (posture.isTranslatedSubstrate() && nativeWitnessRef !== undefined) { + throw new WarpError('translated substrate evidence must not carry nativeWitnessRef', 'E_VALIDATION'); + } +} diff --git a/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts b/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts new file mode 100644 index 00000000..91ab62b5 --- /dev/null +++ b/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; +import ContinuumEvidencePosture from '../../../../src/domain/continuum/ContinuumEvidencePosture.ts'; +import ContinuumEvidenceStatus from '../../../../src/domain/continuum/ContinuumEvidenceStatus.ts'; + +const PATCH_BASIS_REF = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; +const NATIVE_WITNESS_REF = 'continuum:witness:receipt-family:1'; + +describe('ContinuumEvidenceStatus', () => { + it('marks git-warp evidence as translated substrate evidence', () => { + const status = ContinuumEvidenceStatus.translatedGitWarp({ + basisRef: PATCH_BASIS_REF, + summary: 'git-warp patch receipt projected into receipt-family shape', + }); + + expect(status.posture.toString()).toBe('translated-substrate'); + expect(status.sourceRuntime).toBe('git-warp'); + expect(status.basisRef).toBe(PATCH_BASIS_REF); + expect(status.nativeWitnessRef).toBeUndefined(); + expect(status.isTranslatedSubstrate()).toBe(true); + expect(status.isContinuumNative()).toBe(false); + expect(Object.isFrozen(status)).toBe(true); + }); + + it('accepts native Continuum evidence only with an explicit native witness reference', () => { + const status = new ContinuumEvidenceStatus({ + posture: 'continuum-native', + sourceRuntime: 'git-warp', + basisRef: PATCH_BASIS_REF, + nativeWitnessRef: NATIVE_WITNESS_REF, + summary: 'receipt-family value was produced through native Continuum witnesshood', + }); + + expect(status.posture.toString()).toBe('continuum-native'); + expect(status.nativeWitnessRef).toBe(NATIVE_WITNESS_REF); + expect(status.isContinuumNative()).toBe(true); + expect(status.isTranslatedSubstrate()).toBe(false); + }); + + it('rejects native Continuum evidence without a native witness reference', () => { + expect(() => new ContinuumEvidenceStatus({ + posture: 'continuum-native', + sourceRuntime: 'git-warp', + basisRef: PATCH_BASIS_REF, + summary: 'missing witness', + })).toThrow('nativeWitnessRef'); + }); + + it('rejects translated substrate evidence that carries a native witness reference', () => { + expect(() => new ContinuumEvidenceStatus({ + posture: 'translated-substrate', + sourceRuntime: 'git-warp', + basisRef: PATCH_BASIS_REF, + nativeWitnessRef: NATIVE_WITNESS_REF, + summary: 'translated evidence cannot claim native witnesshood', + })).toThrow('translated substrate evidence must not carry nativeWitnessRef'); + }); +}); + +describe('ContinuumEvidencePosture', () => { + it('rejects unknown posture values', () => { + expect(() => new ContinuumEvidencePosture('fixture-only')).toThrow('Continuum evidence posture'); + }); +}); diff --git a/test/unit/domain/index.exports.test.ts b/test/unit/domain/index.exports.test.ts index c96d5be5..0ae15e54 100644 --- a/test/unit/domain/index.exports.test.ts +++ b/test/unit/domain/index.exports.test.ts @@ -61,6 +61,8 @@ import WarpAppDefault, { ContinuumArtifactAuthority, ContinuumArtifactDescriptor, ContinuumArtifactIngestionPolicy, + ContinuumEvidencePosture, + ContinuumEvidenceStatus, ContinuumFamilyId, ContinuumArtifactJsonFileAdapter, } from '../../../index.ts'; @@ -262,6 +264,8 @@ describe('index.ts exports', () => { expect(ContinuumArtifactAuthority).toBeDefined(); expect(ContinuumArtifactDescriptor).toBeDefined(); expect(ContinuumArtifactIngestionPolicy).toBeDefined(); + expect(ContinuumEvidencePosture).toBeDefined(); + expect(ContinuumEvidenceStatus).toBeDefined(); expect(ContinuumFamilyId).toBeDefined(); expect(ContinuumArtifactJsonFileAdapter).toBeDefined(); }); @@ -280,6 +284,16 @@ describe('index.ts exports', () => { expect(descriptor.familyId).toBeInstanceOf(ContinuumFamilyId); expect(descriptor.hasGeneratedAuthority()).toBe(true); }); + + it('constructs translated git-warp evidence status from public exports', () => { + const status = ContinuumEvidenceStatus.translatedGitWarp({ + basisRef: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + summary: 'git-warp evidence translated into Continuum shape', + }); + + expect(status.posture).toBeInstanceOf(ContinuumEvidencePosture); + expect(status.isTranslatedSubstrate()).toBe(true); + }); }); describe('cancellation utilities', () => { From 9b0c974b8333f34210c766e3f2665d52f3a174ab Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 00:05:11 -0700 Subject: [PATCH 2/7] fix: enforce patch commit visibility --- docs/BEARING.md | 9 +- .../v18-patch-commit-visibility.md | 136 ++++++++++++++++++ .../v18-patch-commit-visibility.md | 57 ++++++++ src/domain/errors/WriterError.ts | 5 + src/domain/services/PatchCommitter.ts | 44 +++++- .../domain/services/PatchBuilder.cas.test.ts | 19 ++- .../PatchCommitter.visibility.test.ts | 111 ++++++++++++++ 7 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 docs/design/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md create mode 100644 docs/method/retro/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md create mode 100644 test/unit/domain/services/PatchCommitter.visibility.test.ts diff --git a/docs/BEARING.md b/docs/BEARING.md index c964020a..e7e3e3b9 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -185,8 +185,13 @@ before the final commit for that slice, and mark completed items with `- [x]`. `translated-substrate` evidence, native Continuum evidence cannot be constructed without `nativeWitnessRef`, and translated evidence rejects native witness references. -- [ ] 7. Prove the patch commit visibility contract: success means canonical - writer-tip advancement and visible graph truth, not just object creation. +- [x] 7. Prove the patch commit visibility contract: success means canonical + writer-tip advancement and visible graph truth, not just object creation. The + patch commit path now advances writer refs with `compareAndSwapRef()`, + rereads the writer ref before returning success, throws + `WRITER_COMMIT_NOT_VISIBLE` if the returned commit is not the visible writer + tip, and has focused tests proving both ref visibility and materialized graph + visibility. - [ ] 8. Add the same-writer concurrent patch race witness with final-frontier and visible-state assertions. - [ ] 9. Project git-warp receipt facts into the generated Continuum diff --git a/docs/design/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md b/docs/design/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md new file mode 100644 index 00000000..18cde7a4 --- /dev/null +++ b/docs/design/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md @@ -0,0 +1,136 @@ +--- +cycle: 0151 +task_id: V18_patch_commit_visibility +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Patch Commit Visibility + +## Pull + +Receipt-family projection depends on the source fact underneath it: a successful +patch commit must mean canonical writer-tip advancement and visible graph truth, +not merely Git object creation. + +## Hill + +Patch commit success is reported only after the writer ref is atomically +advanced to the returned patch commit and the returned commit is visible through +materialization. + +## Playback Questions + +Agent: + +- Does patch commit use the persistence CAS ref surface for writer-tip + advancement? +- Does commit reject success when the writer ref does not point at the returned + commit after CAS? +- Does a successful graph patch become visible through materialization? + +Human: + +- Can later receipt-family projections trust a returned patch SHA as the + canonical writer-tip fact? + +## Accessibility / Assistive Reading Posture + +No user-facing visual surface changes. The contract is asserted through tests +and error codes. + +## Localization / Directionality Posture + +No localized strings are introduced. Error messages remain developer-facing. + +## Agent Inspectability / Explainability Posture + +The visibility failure path uses a stable error code so agents can distinguish +object creation from canonical writer-tip visibility. + +## Non-Goals + +- Do not change checkpoint, strand, or audit ref update behavior. +- Do not project receipt-family values yet. +- Do not rewrite existing patch history. + +## RED + +Expected failing spec: + +```text +npx vitest run test/unit/domain/services/PatchCommitter.visibility.test.ts +``` + +Observed RED: + +```text +expected [] to deeply equal [{ ref, newOid, expectedOid }] +promise resolved instead of rejecting +``` + +The old path used plain `updateRef()` and did not verify the post-update writer +ref before returning success. + +## GREEN + +This slice changes patch commit persistence to: + +1. create the patch commit object, +2. atomically advance the writer ref with `compareAndSwapRef()`, +3. reread the writer ref, +4. report success only when the writer ref points at the returned commit SHA. + +If the post-CAS visibility check fails, the commit path throws +`WriterError` with code `WRITER_COMMIT_NOT_VISIBLE`. + +## Playback + +Witness: + +```text +npx vitest run test/unit/domain/services/PatchCommitter.visibility.test.ts test/unit/domain/services/PatchBuilder.cas.test.ts +Test Files 2 passed (2) +Tests 10 passed (10) + +npm run typecheck:src -- --pretty false +npm run typecheck:test -- --pretty false +npm run lint:sludge +npx eslint --no-warn-ignored src/domain/services/PatchCommitter.ts src/domain/errors/WriterError.ts test/unit/domain/services/PatchCommitter.visibility.test.ts test/unit/domain/services/PatchBuilder.cas.test.ts +git diff --check +``` + +Agent answers: + +- Yes, patch commit uses the CAS ref surface for writer-tip advancement. +- Yes, commit rejects success when the writer ref does not point at the + returned commit after CAS. +- Yes, the successful graph patch test proves materialization-visible graph + truth. + +Human answer: + +- Later receipt-family projections can trust a returned patch SHA as canonical + writer-tip evidence after successful commit. + +## SSJS Scorecard + +- Runtime-backed forms: green; existing `WriterError` carries the new stable + visibility code. +- Boundary validation: green; persistence stays behind the ref port. +- Behavior ownership: green; commit visibility is enforced inside the patch + commit path that owns patch persistence. +- Message parsing: green; no message parsing introduced. +- Ambient time or entropy: green; no ambient time or entropy introduced. +- Fake shape trust or cast-cosplay: green; success is now checked against the + canonical writer ref instead of inferred from object creation. + +## Closeout + +This closes BEARING task 7 and gives receipt projection a stronger source fact: +successful patch commit means canonical writer-tip visibility. diff --git a/docs/method/retro/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md b/docs/method/retro/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md new file mode 100644 index 00000000..05d18374 --- /dev/null +++ b/docs/method/retro/0151-v18-patch-commit-visibility/v18-patch-commit-visibility.md @@ -0,0 +1,57 @@ +--- +cycle: 0151 +task_id: V18_patch_commit_visibility +status: Complete +sponsors: + human: James + agent: Codex +completed_at: 2026-05-21 +--- + +# Retro: V18 Patch Commit Visibility + +## Hill + +Patch commit success is reported only after the writer ref is atomically +advanced to the returned patch commit and the returned commit is visible through +materialization. + +## Result + +Hill met. + +## Witness + +```text +npx vitest run test/unit/domain/services/PatchCommitter.visibility.test.ts test/unit/domain/services/PatchBuilder.cas.test.ts +Test Files 2 passed (2) +Tests 10 passed (10) + +npm run typecheck:src -- --pretty false +npm run typecheck:test -- --pretty false +npm run lint:sludge +npx eslint --no-warn-ignored src/domain/services/PatchCommitter.ts src/domain/errors/WriterError.ts test/unit/domain/services/PatchCommitter.visibility.test.ts test/unit/domain/services/PatchBuilder.cas.test.ts +git diff --check +``` + +## Drift Check + +No drift. The slice stayed on patch commit success semantics and did not touch +checkpoint, audit, strand, or receipt projection behavior. + +## What Mess We Got Into + +Patch commit had the right preflight CAS check but then used non-CAS ref update +for the final writer-tip move. It also treated object creation as enough for +success. + +## What Mess We Got Out Of + +The final ref move is now CAS-backed and success requires the writer ref to +name the returned commit. If the ref is not visible, the error code says so. + +## What Comes Next + +Use this stronger commit contract to test same-writer concurrent patch races: +only one stale concurrent builder may win, and only the winning patch may become +visible graph truth. diff --git a/src/domain/errors/WriterError.ts b/src/domain/errors/WriterError.ts index c63d366c..f0c68661 100644 --- a/src/domain/errors/WriterError.ts +++ b/src/domain/errors/WriterError.ts @@ -14,12 +14,15 @@ import WarpError from './WarpError.ts'; * | `EMPTY_PATCH` | Patch commit attempted with zero operations | * | `WRITER_REF_ADVANCED` | Writer ref moved since beginPatch() | * | `WRITER_CAS_CONFLICT` | Compare-and-swap failure during commit | + * | `WRITER_COMMIT_NOT_VISIBLE` | Returned commit is not the writer ref tip after CAS | * | `PERSIST_WRITE_FAILED` | Git persistence operation failed | * | `NO_BLOB_STORAGE` | Content attachment attempted without blob storage | * | `WRITER_ERROR` | Generic/default writer error | */ export default class WriterError extends WarpError { declare cause: Error | undefined; + expectedSha: string | null | undefined; + actualSha: string | null | undefined; /** * Note: constructor parameter order differs from other WarpError subclasses @@ -29,6 +32,8 @@ export default class WriterError extends WarpError { */ constructor(code: string, message: string, cause?: Error) { super(message, 'WRITER_ERROR', { code }); + this.expectedSha = undefined; + this.actualSha = undefined; if (cause !== undefined) { this.cause = cause; } diff --git a/src/domain/services/PatchCommitter.ts b/src/domain/services/PatchCommitter.ts index 86949311..cc06638d 100644 --- a/src/domain/services/PatchCommitter.ts +++ b/src/domain/services/PatchCommitter.ts @@ -70,7 +70,7 @@ export async function commitPatch(state: CommitState): Promise { const err = new WriterError( 'WRITER_CAS_CONFLICT', 'Commit failed: writer ref was updated by another process. Re-materialize and retry.', - ) as WriterError & { expectedSha: string | null; actualSha: string | null }; + ); err.expectedSha = state.expectedParentSha; err.actualSha = currentRefSha; throw err; @@ -149,8 +149,13 @@ export async function commitPatch(state: CommitState): Promise { treeOid, parents, message, }); - // Update writer ref - await state.persistence.updateRef(writerRef, newCommitSha); + // Atomically advance writer ref and verify the returned commit is canonical. + await advanceWriterRef({ + persistence: state.persistence, + writerRef, + newCommitSha, + expectedParentSha: currentRefSha, + }); // Invoke success callback if (state.onCommitSuccess) { @@ -164,3 +169,36 @@ export async function commitPatch(state: CommitState): Promise { return newCommitSha; } + +async function advanceWriterRef(options: { + persistence: PersistencePorts; + writerRef: string; + newCommitSha: string; + expectedParentSha: string | null; +}): Promise { + try { + await options.persistence.compareAndSwapRef( + options.writerRef, + options.newCommitSha, + options.expectedParentSha, + ); + } catch (err) { + const actualSha = await options.persistence.readRef(options.writerRef); + const error = new WriterError( + 'WRITER_CAS_CONFLICT', + 'Commit failed: writer ref was updated by another process. Re-materialize and retry.', + err instanceof Error ? err : undefined, + ); + error.expectedSha = options.expectedParentSha; + error.actualSha = actualSha; + throw error; + } + + const visibleSha = await options.persistence.readRef(options.writerRef); + if (visibleSha !== options.newCommitSha) { + throw new WriterError( + 'WRITER_COMMIT_NOT_VISIBLE', + `Commit ${options.newCommitSha} was written but ${options.writerRef} points at ${visibleSha ?? '(none)'}`, + ); + } +} diff --git a/test/unit/domain/services/PatchBuilder.cas.test.ts b/test/unit/domain/services/PatchBuilder.cas.test.ts index 34ab2704..6705a594 100644 --- a/test/unit/domain/services/PatchBuilder.cas.test.ts +++ b/test/unit/domain/services/PatchBuilder.cas.test.ts @@ -19,6 +19,7 @@ function createMockPersistence(overrides = {}): any { writeTree: vi.fn().mockResolvedValue('b'.repeat(40)), commitNodeWithTree: vi.fn().mockResolvedValue('c'.repeat(40)), updateRef: vi.fn().mockResolvedValue(undefined), + compareAndSwapRef: vi.fn().mockResolvedValue(undefined), ...overrides, }; } @@ -200,8 +201,11 @@ describe('PatchBuilder CAS conflict detection', () => { // --------------------------------------------------------------- describe('when no CAS conflict occurs', () => { it('succeeds when expectedParentSha matches current ref (both null)', async () => { + const commitSha = 'c'.repeat(40); const persistence = createMockPersistence({ - readRef: vi.fn().mockResolvedValue(null), + readRef: vi.fn() + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(commitSha), }); const builder = new PatchBuilder({ @@ -218,17 +222,21 @@ describe('PatchBuilder CAS conflict detection', () => { builder.addNode('x'); const sha = await builder.commit(); - expect(sha).toBe('c'.repeat(40)); + expect(sha).toBe(commitSha); expect(persistence.commitNodeWithTree).toHaveBeenCalledOnce(); - expect(persistence.updateRef).toHaveBeenCalledOnce(); + expect(persistence.compareAndSwapRef).toHaveBeenCalledOnce(); + expect(persistence.updateRef).not.toHaveBeenCalled(); }); it('succeeds when expectedParentSha matches current ref (both same SHA)', async () => { const parentSha = 'd'.repeat(40); const patchOid = 'e'.repeat(40); + const commitSha = 'c'.repeat(40); const persistence = createMockPersistence({ - readRef: vi.fn().mockResolvedValue(parentSha), + readRef: vi.fn() + .mockResolvedValueOnce(parentSha) + .mockResolvedValueOnce(commitSha), showNode: vi.fn().mockResolvedValue( `warp:patch\n\neg-kind: patch\neg-graph: test-graph\neg-writer: writer1\neg-lamport: 3\neg-patch-oid: ${patchOid}\neg-schema: 2` ), @@ -248,8 +256,9 @@ describe('PatchBuilder CAS conflict detection', () => { builder.addNode('x'); const sha = await builder.commit(); - expect(sha).toBe('c'.repeat(40)); + expect(sha).toBe(commitSha); expect(persistence.commitNodeWithTree).toHaveBeenCalledOnce(); + expect(persistence.compareAndSwapRef).toHaveBeenCalledOnce(); }); }); }); diff --git a/test/unit/domain/services/PatchCommitter.visibility.test.ts b/test/unit/domain/services/PatchCommitter.visibility.test.ts new file mode 100644 index 00000000..ec68da5a --- /dev/null +++ b/test/unit/domain/services/PatchCommitter.visibility.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it } from 'vitest'; +import InMemoryGraphAdapter from '../../../../src/infrastructure/adapters/InMemoryGraphAdapter.ts'; +import { CborPatchJournalAdapter } from '../../../../src/infrastructure/adapters/CborPatchJournalAdapter.ts'; +import { CborCodec } from '../../../../src/infrastructure/codecs/CborCodec.ts'; +import VersionVector from '../../../../src/domain/crdt/VersionVector.ts'; +import { PatchBuilder } from '../../../../src/domain/services/PatchBuilder.ts'; +import { buildWriterRef } from '../../../../src/domain/utils/RefLayout.ts'; +import WriterError from '../../../../src/domain/errors/WriterError.ts'; +import { openRuntimeHostProduct } from '../../../../src/domain/warp/RuntimeHostProduct.ts'; + +const GRAPH_NAME = 'visibility'; +const WRITER_ID = 'writer-a'; +const DRIFT_SHA = 'd'.repeat(40); + +type CasUpdate = { + readonly ref: string; + readonly newOid: string; + readonly expectedOid: string | null; +}; + +class RecordingGraphAdapter extends InMemoryGraphAdapter { + readonly casUpdates: CasUpdate[] = []; + + override async compareAndSwapRef( + ref: string, + newOid: string, + expectedOid: string | null, + ): Promise { + this.casUpdates.push(Object.freeze({ ref, newOid, expectedOid })); + await super.compareAndSwapRef(ref, newOid, expectedOid); + } +} + +class DriftAfterCasGraphAdapter extends RecordingGraphAdapter { + override async compareAndSwapRef( + ref: string, + newOid: string, + expectedOid: string | null, + ): Promise { + await super.compareAndSwapRef(ref, newOid, expectedOid); + await super.updateRef(ref, DRIFT_SHA); + } +} + +function createPatchJournal(persistence: InMemoryGraphAdapter): CborPatchJournalAdapter { + return new CborPatchJournalAdapter({ + codec: new CborCodec(), + blobPort: persistence, + }); +} + +function createBuilder(persistence: InMemoryGraphAdapter): PatchBuilder { + return new PatchBuilder({ + persistence, + patchJournal: createPatchJournal(persistence), + graphName: GRAPH_NAME, + writerId: WRITER_ID, + lamport: 1, + versionVector: VersionVector.empty(), + getCurrentState: () => null, + expectedParentSha: null, + }); +} + +describe('PatchCommitter visibility contract', () => { + it('advances the writer ref with compare-and-swap before reporting success', async () => { + const persistence = new RecordingGraphAdapter(); + const writerRef = buildWriterRef(GRAPH_NAME, WRITER_ID); + const builder = createBuilder(persistence); + + builder.addNode('node:visible'); + const sha = await builder.commit(); + + expect(persistence.casUpdates).toEqual([{ + ref: writerRef, + newOid: sha, + expectedOid: null, + }]); + expect(await persistence.readRef(writerRef)).toBe(sha); + }); + + it('rejects success when the post-CAS writer ref does not name the returned commit', async () => { + const persistence = new DriftAfterCasGraphAdapter(); + const builder = createBuilder(persistence); + + builder.addNode('node:drift'); + + await expect(builder.commit()).rejects.toMatchObject({ + code: 'WRITER_COMMIT_NOT_VISIBLE', + }); + }); + + it('makes the returned patch commit visible through graph materialization', async () => { + const persistence = new RecordingGraphAdapter(); + const graph = await openRuntimeHostProduct({ + persistence, + graphName: GRAPH_NAME, + writerId: WRITER_ID, + autoMaterialize: true, + }); + const writerRef = buildWriterRef(GRAPH_NAME, WRITER_ID); + + const sha = await graph.patch((patch) => { + patch.addNode('node:materialized'); + }); + + expect(await persistence.readRef(writerRef)).toBe(sha); + await graph.materialize(); + expect(await graph.hasNode('node:materialized')).toBe(true); + }); +}); From 19bdaa730194ecbf863073907a9b39e9497d005d Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 00:07:33 -0700 Subject: [PATCH 3/7] test: witness same-writer patch race --- docs/BEARING.md | 7 +- .../v18-same-writer-race-witness.md | 133 ++++++++++++++++++ .../v18-same-writer-race-witness.md | 52 +++++++ .../domain/WarpGraph.sameWriterRace.test.ts | 86 +++++++++++ 4 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 docs/design/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md create mode 100644 docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md create mode 100644 test/unit/domain/WarpGraph.sameWriterRace.test.ts diff --git a/docs/BEARING.md b/docs/BEARING.md index e7e3e3b9..5f59f965 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -192,8 +192,11 @@ before the final commit for that slice, and mark completed items with `- [x]`. `WRITER_COMMIT_NOT_VISIBLE` if the returned commit is not the visible writer tip, and has focused tests proving both ref visibility and materialized graph visibility. -- [ ] 8. Add the same-writer concurrent patch race witness with final-frontier - and visible-state assertions. +- [x] 8. Add the same-writer concurrent patch race witness with final-frontier + and visible-state assertions. The regression witness creates two stale + builders for the same writer, commits them concurrently, proves exactly one + wins, asserts the writer ref names the winning SHA, and verifies only the + winning node is visible after materialization. - [ ] 9. Project git-warp receipt facts into the generated Continuum receipt-family shape with conformance tests. - [ ] 10. Add the first `warp-ttd` smoke over generated-family git-warp receipt diff --git a/docs/design/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md b/docs/design/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md new file mode 100644 index 00000000..78745036 --- /dev/null +++ b/docs/design/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md @@ -0,0 +1,133 @@ +--- +cycle: 0152 +task_id: V18_same_writer_race_witness +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Same-Writer Race Witness + +## Pull + +The commit visibility contract is stronger, but v18 receipt projection also +needs proof that concurrent same-writer builders cannot both become visible +truth. + +## Hill + +A same-writer concurrent patch race has a regression witness proving exactly +one stale builder wins, the final writer frontier names the winning commit, and +only the winning patch is visible after materialization. + +## Playback Questions + +Agent: + +- Do two concurrent builders for the same writer settle with exactly one + successful commit? +- Does the writer ref point at the winning commit SHA? +- Does materialized graph state include only the winning patch's node? + +Human: + +- Can receipt-family projection ignore losing same-writer race objects because + they are not canonical writer-tip history? + +## Accessibility / Assistive Reading Posture + +No visual surface changes. The witness is an executable regression test. + +## Localization / Directionality Posture + +No localized strings are introduced. + +## Agent Inspectability / Explainability Posture + +The test records winning SHA, frontier, and visible state assertions in a way +agents can rerun. + +## Non-Goals + +- Do not change multi-writer coexistence semantics. +- Do not change conflict analysis or strand behavior. +- Do not project receipt-family values yet. + +## RED + +Expected failing spec if the CAS/visibility contract regresses: + +```text +npx vitest run test/unit/domain/WarpGraph.sameWriterRace.test.ts +``` + +Observed result after slice 7 CAS hardening: + +```text +Test Files 1 passed (1) +Tests 1 passed (1) +``` + +This is a regression witness rather than a behavior-changing slice. It would +have been unstable or false before the commit path had CAS-backed final +frontier visibility. + +## GREEN + +This slice adds `WarpGraph.sameWriterRace.test.ts`. The test creates two +patch builders for the same writer from the same expected parent, commits both +concurrently, and asserts: + +- exactly one commit wins; +- exactly one stale builder is rejected with `WRITER_CAS_CONFLICT`; +- the final writer ref names the winning SHA; +- the winning node is visible after materialization; +- the losing node is not visible after materialization. + +## Playback + +Witness: + +```text +npx vitest run test/unit/domain/WarpGraph.sameWriterRace.test.ts test/unit/domain/services/PatchCommitter.visibility.test.ts +Test Files 2 passed (2) +Tests 4 passed (4) + +npm run typecheck:test -- --pretty false +npx eslint --no-warn-ignored test/unit/domain/WarpGraph.sameWriterRace.test.ts +``` + +Agent answers: + +- Yes, two concurrent same-writer builders settle with exactly one successful + commit. +- Yes, the writer ref points at the winning commit SHA. +- Yes, materialized graph state includes the winning node and excludes the + losing node. + +Human answer: + +- Receipt-family projection can ignore losing same-writer race objects because + they are not canonical writer-tip history. + +## SSJS Scorecard + +- Runtime-backed forms: green; no new runtime model was required. +- Boundary validation: green; the test exercises the runtime through existing + patch and persistence ports. +- Behavior ownership: green; race semantics remain owned by patch commit and + writer ref behavior. +- Message parsing: green; assertions use error code and graph state, not error + text. +- Ambient time or entropy: green; no ambient time or entropy introduced. +- Fake shape trust or cast-cosplay: green; the witness checks final frontier + and visible state directly. + +## Closeout + +This closes BEARING task 8 and protects the receipt source stream against +same-writer stale-builder races. diff --git a/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md b/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md new file mode 100644 index 00000000..8ae3731d --- /dev/null +++ b/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md @@ -0,0 +1,52 @@ +--- +cycle: 0152 +task_id: V18_same_writer_race_witness +status: Complete +sponsors: + human: James + agent: Codex +completed_at: 2026-05-21 +--- + +# Retro: V18 Same-Writer Race Witness + +## Hill + +A same-writer concurrent patch race has a regression witness proving exactly +one stale builder wins, the final writer frontier names the winning commit, and +only the winning patch is visible after materialization. + +## Result + +Hill met. + +## Witness + +```text +npx vitest run test/unit/domain/WarpGraph.sameWriterRace.test.ts test/unit/domain/services/PatchCommitter.visibility.test.ts +Test Files 2 passed (2) +Tests 4 passed (4) + +npm run typecheck:test -- --pretty false +npx eslint --no-warn-ignored test/unit/domain/WarpGraph.sameWriterRace.test.ts +``` + +## Drift Check + +No drift. This was intentionally a witness slice. It did not alter runtime code +after the slice 7 CAS visibility hardening. + +## What Mess We Got Into + +Before projecting receipts, we needed to prove stale same-writer builders do +not both become canonical history just because both can create patch objects. + +## What Mess We Got Out Of + +The new witness pins the final frontier and the visible graph state. One stale +builder wins; the losing builder is not graph truth. + +## What Comes Next + +Project `TickReceipt` facts into Continuum receipt-family `Receipt` facts with +translated git-warp evidence posture. diff --git a/test/unit/domain/WarpGraph.sameWriterRace.test.ts b/test/unit/domain/WarpGraph.sameWriterRace.test.ts new file mode 100644 index 00000000..62ecc8ee --- /dev/null +++ b/test/unit/domain/WarpGraph.sameWriterRace.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest'; +import InMemoryGraphAdapter from '../../../src/infrastructure/adapters/InMemoryGraphAdapter.ts'; +import { openRuntimeHostProduct } from '../../../src/domain/warp/RuntimeHostProduct.ts'; +import { buildWriterRef } from '../../../src/domain/utils/RefLayout.ts'; +import WriterError from '../../../src/domain/errors/WriterError.ts'; + +const GRAPH_NAME = 'same-writer-race'; +const WRITER_ID = 'writer-a'; +const FIRST_NODE = 'node:first'; +const SECOND_NODE = 'node:second'; + +function fulfilledCommitShas(results: readonly PromiseSettledResult[]): string[] { + const shas: string[] = []; + for (const result of results) { + if (result.status === 'fulfilled') { + shas.push(result.value); + } + } + return shas; +} + +function rejectedWriterErrors(results: readonly PromiseSettledResult[]): WriterError[] { + const errors: WriterError[] = []; + for (const result of results) { + if (result.status === 'rejected' && result.reason instanceof WriterError) { + errors.push(result.reason); + } + } + return errors; +} + +function winningNodeId(results: readonly PromiseSettledResult[]): string { + if (results[0]?.status === 'fulfilled') { + return FIRST_NODE; + } + return SECOND_NODE; +} + +function losingNodeId(results: readonly PromiseSettledResult[]): string { + if (results[0]?.status === 'fulfilled') { + return SECOND_NODE; + } + return FIRST_NODE; +} + +describe('same-writer concurrent patch race', () => { + it('leaves only the winning stale builder on the final frontier and visible state', async () => { + const persistence = new InMemoryGraphAdapter(); + const graph = await openRuntimeHostProduct({ + persistence, + graphName: GRAPH_NAME, + writerId: WRITER_ID, + autoMaterialize: true, + }); + + const firstPatch = await graph.createPatch(); + firstPatch.addNode(FIRST_NODE); + + const secondPatch = await graph.createPatch(); + secondPatch.addNode(SECOND_NODE); + + const results = await Promise.allSettled([ + firstPatch.commit(), + secondPatch.commit(), + ]); + const winners = fulfilledCommitShas(results); + const rejected = rejectedWriterErrors(results); + + expect(winners).toHaveLength(1); + expect(rejected).toHaveLength(1); + expect(rejected[0]?.code).toBe('WRITER_CAS_CONFLICT'); + + const writerRef = buildWriterRef(GRAPH_NAME, WRITER_ID); + expect(await persistence.readRef(writerRef)).toBe(winners[0]); + + await graph.materialize(); + const winnerNode = winningNodeId(results); + const loserNode = losingNodeId(results); + const firstVisible = await graph.hasNode(FIRST_NODE); + const secondVisible = await graph.hasNode(SECOND_NODE); + + expect([firstVisible, secondVisible].filter(Boolean)).toHaveLength(1); + expect(await graph.hasNode(winnerNode)).toBe(true); + expect(await graph.hasNode(loserNode)).toBe(false); + }); +}); From 149fa159281f2d5f3849f69cf697e95a96b949e6 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 00:11:58 -0700 Subject: [PATCH 4/7] feat: project continuum receipt facts --- docs/BEARING.md | 8 +- .../v18-receipt-family-projection.md | 142 ++++++++++++++++++ .../v18-receipt-family-projection.md | 55 +++++++ index.ts | 12 ++ src/domain/continuum/ContinuumReceipt.ts | 81 ++++++++++ .../ContinuumReceiptFamilyProjection.ts | 49 ++++++ .../continuum/ContinuumReceiptProjector.ts | 59 ++++++++ .../ContinuumReceiptProjection.test.ts | 104 +++++++++++++ test/unit/domain/index.exports.test.ts | 6 + 9 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md create mode 100644 docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md create mode 100644 src/domain/continuum/ContinuumReceipt.ts create mode 100644 src/domain/continuum/ContinuumReceiptFamilyProjection.ts create mode 100644 src/domain/continuum/ContinuumReceiptProjector.ts create mode 100644 test/unit/domain/continuum/ContinuumReceiptProjection.test.ts diff --git a/docs/BEARING.md b/docs/BEARING.md index 5f59f965..c5d7176b 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -197,8 +197,12 @@ before the final commit for that slice, and mark completed items with `- [x]`. builders for the same writer, commits them concurrently, proves exactly one wins, asserts the writer ref names the winning SHA, and verifies only the winning node is visible after materialization. -- [ ] 9. Project git-warp receipt facts into the generated Continuum - receipt-family shape with conformance tests. +- [x] 9. Project git-warp receipt facts into the generated Continuum + receipt-family shape with conformance tests. `ContinuumReceiptProjector` now + maps `TickReceipt` into runtime-backed Continuum receipt-family `Receipt` + facts, `ContinuumReceiptFamilyProjection` carries the receipt-family artifact + descriptor and explicit evidence status, and non-receipt-family artifacts are + rejected. - [ ] 10. Add the first `warp-ttd` smoke over generated-family git-warp receipt facts instead of handwritten adapter-local receipt folklore. - [ ] 11. Re-plan with evidence in hand before expanding into reading-envelope, diff --git a/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md b/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md new file mode 100644 index 00000000..59f14540 --- /dev/null +++ b/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md @@ -0,0 +1,142 @@ +--- +cycle: 0153 +task_id: V18_receipt_family_projection +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Receipt Family Projection + +## Pull + +The repo can admit generated receipt-family artifacts and can now distinguish +translated substrate evidence from native Continuum evidence. The next step is +to project real `git-warp` receipt facts into the generated Continuum +receipt-family shape. + +## Hill + +`TickReceipt` values can be projected into Continuum receipt-family `Receipt` +facts with generated artifact authority and explicit translated git-warp +evidence posture. + +## Playback Questions + +Agent: + +- Does the projector map `TickReceipt` fields to the receipt-family `Receipt` + shape? +- Does the projection carry generated artifact authority and explicit evidence + status? +- Does the projection reject non-receipt-family artifacts? + +Human: + +- Can `warp-ttd` receive receipt-family facts from `git-warp` instead of + reverse-engineering local `TickReceipt` folklore? + +## Accessibility / Assistive Reading Posture + +The projection is inspectable structured data. No visual-only state is +introduced. + +## Localization / Directionality Posture + +Protocol identifiers are not localized. Summaries are plain strings that can be +localized later at product boundaries. + +## Agent Inspectability / Explainability Posture + +The projection keeps artifact descriptor, evidence status, and receipt facts as +separate inspectable fields. + +## Non-Goals + +- Do not claim native Continuum witnesshood. +- Do not add delivery observation projection yet. +- Do not call Wesley or parse GraphQL at runtime. + +## RED + +Expected failing spec: + +```text +npx vitest run test/unit/domain/continuum/ContinuumReceiptProjection.test.ts +``` + +Observed RED: + +```text +Error: Cannot find module '../../../../src/domain/continuum/ContinuumReceipt.ts' +``` + +## GREEN + +This slice adds: + +- `ContinuumReceipt` +- `ContinuumReceiptFamilyProjection` +- `ContinuumReceiptProjector` + +`TickReceipt` maps into the Continuum receipt-family `Receipt` shape: + +- `patchSha` becomes `receiptId`, `headId`, and `digest`; +- `lamport` becomes `frameIndex` and `outputTick`; +- `writer` becomes `laneId` and `writerId`; +- superseded operations become rejected rewrites; +- applied and redundant operations become admitted rewrites; +- evidence posture remains separate from the receipt fact. + +The aggregate projection requires a `receipt-family` artifact descriptor and +keeps artifact authority, evidence status, and receipt facts as separate +inspectable fields. + +## Playback + +Witness: + +```text +npx vitest run test/unit/domain/continuum/ContinuumReceiptProjection.test.ts test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts test/unit/domain/index.exports.test.ts +Test Files 3 passed (3) +Tests 58 passed (58) + +npm run typecheck:src -- --pretty false +npm run typecheck:test -- --pretty false +npm run lint:sludge +npx eslint --no-warn-ignored src/domain/continuum/ContinuumReceipt.ts src/domain/continuum/ContinuumReceiptFamilyProjection.ts src/domain/continuum/ContinuumReceiptProjector.ts test/unit/domain/continuum/ContinuumReceiptProjection.test.ts test/unit/domain/index.exports.test.ts index.ts +git diff --check +``` + +Agent answers: + +- Yes, the projector maps `TickReceipt` fields to Continuum `Receipt` fields. +- Yes, the projection carries generated artifact authority and explicit + evidence status. +- Yes, non-receipt-family artifacts are rejected. + +Human answer: + +- `warp-ttd` can now receive receipt-family facts from `git-warp` without + reverse-engineering raw `TickReceipt` shape. + +## SSJS Scorecard + +- Runtime-backed forms: green; receipt and projection are classes with + constructor validation and frozen instances. +- Boundary validation: green; generated artifact authority stays represented + by the descriptor admitted in the previous slice. +- Behavior ownership: green; receipt projection behavior lives in the projector. +- Message parsing: green; no behavior branches parse messages. +- Ambient time or entropy: green; no ambient time or entropy introduced. +- Fake shape trust or cast-cosplay: green; evidence status remains separate and + translated by default. + +## Closeout + +This closes BEARING task 9 and gives the next slice a generated-family receipt +fact set to smoke through the `warp-ttd` consumer posture. diff --git a/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md b/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md new file mode 100644 index 00000000..da9b7518 --- /dev/null +++ b/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md @@ -0,0 +1,55 @@ +--- +cycle: 0153 +task_id: V18_receipt_family_projection +status: Complete +sponsors: + human: James + agent: Codex +completed_at: 2026-05-21 +--- + +# Retro: V18 Receipt Family Projection + +## Hill + +`TickReceipt` values can be projected into Continuum receipt-family `Receipt` +facts with generated artifact authority and explicit translated git-warp +evidence posture. + +## Result + +Hill met. + +## Witness + +```text +npx vitest run test/unit/domain/continuum/ContinuumReceiptProjection.test.ts test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts test/unit/domain/index.exports.test.ts +Test Files 3 passed (3) +Tests 58 passed (58) + +npm run typecheck:src -- --pretty false +npm run typecheck:test -- --pretty false +npm run lint:sludge +npx eslint --no-warn-ignored src/domain/continuum/ContinuumReceipt.ts src/domain/continuum/ContinuumReceiptFamilyProjection.ts src/domain/continuum/ContinuumReceiptProjector.ts test/unit/domain/continuum/ContinuumReceiptProjection.test.ts test/unit/domain/index.exports.test.ts index.ts +git diff --check +``` + +## Drift Check + +No drift. The slice projected `TickReceipt` to receipt-family `Receipt` facts +only. Delivery observations and native Continuum witness production remain +out of scope. + +## What Mess We Got Into + +`warp-ttd` previously had to know too much about raw git-warp `TickReceipt` +shape. That is adapter folklore, not a generated-family contract. + +## What Mess We Got Out Of + +`git-warp` now owns the translation from its local receipt fact into a +Continuum receipt-family `Receipt`, with evidence posture carried separately. + +## What Comes Next + +Add the first `warp-ttd` smoke over the generated-family receipt projection. diff --git a/index.ts b/index.ts index 245ef7e5..b19777f6 100644 --- a/index.ts +++ b/index.ts @@ -214,12 +214,18 @@ import ContinuumArtifactIngestionPolicy from './src/domain/continuum/ContinuumAr import ContinuumEvidencePosture from './src/domain/continuum/ContinuumEvidencePosture.ts'; import ContinuumEvidenceStatus from './src/domain/continuum/ContinuumEvidenceStatus.ts'; import ContinuumFamilyId from './src/domain/continuum/ContinuumFamilyId.ts'; +import ContinuumReceipt from './src/domain/continuum/ContinuumReceipt.ts'; +import ContinuumReceiptFamilyProjection from './src/domain/continuum/ContinuumReceiptFamilyProjection.ts'; +import ContinuumReceiptProjector from './src/domain/continuum/ContinuumReceiptProjector.ts'; import ContinuumArtifactJsonFileAdapter from './src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; import type { ContinuumArtifactAuthorityValue } from './src/domain/continuum/ContinuumArtifactAuthority.ts'; import type { ContinuumArtifactDescriptorFields } from './src/domain/continuum/ContinuumArtifactDescriptor.ts'; import type { ContinuumEvidencePostureValue } from './src/domain/continuum/ContinuumEvidencePosture.ts'; import type { ContinuumEvidenceStatusFields } from './src/domain/continuum/ContinuumEvidenceStatus.ts'; import type { ContinuumFamilyIdValue } from './src/domain/continuum/ContinuumFamilyId.ts'; +import type { ContinuumReceiptFields } from './src/domain/continuum/ContinuumReceipt.ts'; +import type { ContinuumReceiptFamilyProjectionFields } from './src/domain/continuum/ContinuumReceiptFamilyProjection.ts'; +import type { ContinuumReceiptProjectionRequest } from './src/domain/continuum/ContinuumReceiptProjector.ts'; import type { ContinuumArtifactJsonLoadContext } from './src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; export { @@ -340,6 +346,9 @@ export { ContinuumEvidencePosture, ContinuumEvidenceStatus, ContinuumFamilyId, + ContinuumReceipt, + ContinuumReceiptFamilyProjection, + ContinuumReceiptProjector, ContinuumArtifactJsonFileAdapter, // Tick receipts (LIGHTHOUSE) @@ -394,6 +403,9 @@ export type { ContinuumEvidenceStatusFields, ContinuumArtifactJsonLoadContext, ContinuumFamilyIdValue, + ContinuumReceiptFields, + ContinuumReceiptFamilyProjectionFields, + ContinuumReceiptProjectionRequest, }; // WarpApp is the primary product-facing API for v15. diff --git a/src/domain/continuum/ContinuumReceipt.ts b/src/domain/continuum/ContinuumReceipt.ts new file mode 100644 index 00000000..0ae2a4a6 --- /dev/null +++ b/src/domain/continuum/ContinuumReceipt.ts @@ -0,0 +1,81 @@ +import WarpError from '../errors/WarpError.ts'; + +export type ContinuumReceiptFields = { + readonly receiptId: string; + readonly headId: string; + readonly frameIndex: number; + readonly laneId: string; + readonly writerId?: string; + readonly inputTick: number; + readonly outputTick: number; + readonly admittedRewriteCount: number; + readonly rejectedRewriteCount: number; + readonly counterfactualCount: number; + readonly digest: string; + readonly summary: string; +}; + +/** Continuum receipt-family `Receipt` fact projected from git-warp receipts. */ +export default class ContinuumReceipt { + readonly receiptId: string; + readonly headId: string; + readonly frameIndex: number; + readonly laneId: string; + readonly writerId: string | undefined; + readonly inputTick: number; + readonly outputTick: number; + readonly admittedRewriteCount: number; + readonly rejectedRewriteCount: number; + readonly counterfactualCount: number; + readonly digest: string; + readonly summary: string; + + constructor(fields: ContinuumReceiptFields) { + this.receiptId = requireNonEmptyString(fields.receiptId, 'receiptId'); + this.headId = requireNonEmptyString(fields.headId, 'headId'); + this.frameIndex = requireNonNegativeInteger(fields.frameIndex, 'frameIndex'); + this.laneId = requireNonEmptyString(fields.laneId, 'laneId'); + this.writerId = optionalNonEmptyString(fields.writerId, 'writerId'); + this.inputTick = requireNonNegativeInteger(fields.inputTick, 'inputTick'); + this.outputTick = requireNonNegativeInteger(fields.outputTick, 'outputTick'); + this.admittedRewriteCount = requireNonNegativeInteger( + fields.admittedRewriteCount, + 'admittedRewriteCount', + ); + this.rejectedRewriteCount = requireNonNegativeInteger( + fields.rejectedRewriteCount, + 'rejectedRewriteCount', + ); + this.counterfactualCount = requireNonNegativeInteger( + fields.counterfactualCount, + 'counterfactualCount', + ); + this.digest = requireNonEmptyString(fields.digest, 'digest'); + this.summary = requireNonEmptyString(fields.summary, 'summary'); + Object.freeze(this); + } +} + +/** Validates a required non-empty string. */ +function requireNonEmptyString(value: string, name: string): string { + if (typeof value !== 'string' || value.length === 0) { + throw new WarpError(`${name} must be a non-empty string`, 'E_VALIDATION'); + } + return value; +} + +/** Validates an optional non-empty string. */ +function optionalNonEmptyString(value: string | undefined, name: string): string | undefined { + if (value === undefined) { + return undefined; + } + return requireNonEmptyString(value, name); +} + +/** Validates a non-negative integer. */ +function requireNonNegativeInteger(value: number, name: string): number { + if (!Number.isInteger(value) || value < 0) { + throw new WarpError(`${name} must be a non-negative integer`, 'E_VALIDATION'); + } + return value; +} diff --git a/src/domain/continuum/ContinuumReceiptFamilyProjection.ts b/src/domain/continuum/ContinuumReceiptFamilyProjection.ts new file mode 100644 index 00000000..2ac6dba1 --- /dev/null +++ b/src/domain/continuum/ContinuumReceiptFamilyProjection.ts @@ -0,0 +1,49 @@ +import WarpError from '../errors/WarpError.ts'; +import type ContinuumArtifactDescriptor from './ContinuumArtifactDescriptor.ts'; +import type ContinuumEvidenceStatus from './ContinuumEvidenceStatus.ts'; +import ContinuumFamilyId from './ContinuumFamilyId.ts'; +import type ContinuumReceipt from './ContinuumReceipt.ts'; + +const RECEIPT_FAMILY_ID = new ContinuumFamilyId('receipt-family'); + +export type ContinuumReceiptFamilyProjectionFields = { + readonly artifact: ContinuumArtifactDescriptor; + readonly evidence: ContinuumEvidenceStatus; + readonly receipts: readonly ContinuumReceipt[]; +}; + +/** Receipt-family facts projected from git-warp substrate evidence. */ +export default class ContinuumReceiptFamilyProjection { + readonly artifact: ContinuumArtifactDescriptor; + readonly evidence: ContinuumEvidenceStatus; + readonly receipts: readonly ContinuumReceipt[]; + + constructor(fields: ContinuumReceiptFamilyProjectionFields) { + requireReceiptFamilyArtifact(fields.artifact); + this.artifact = fields.artifact; + this.evidence = fields.evidence; + this.receipts = freezeReceipts(fields.receipts); + Object.freeze(this); + } + + /** Returns receipt-family facts for a head and optional frame index. */ + receiptsForHead(headId: string, frameIndex?: number): readonly ContinuumReceipt[] { + return this.receipts.filter((receipt) => ( + receipt.headId === headId && + (frameIndex === undefined || receipt.frameIndex === frameIndex) + )); + } +} + +/** Requires a generated artifact descriptor for the receipt family. */ +function requireReceiptFamilyArtifact(artifact: ContinuumArtifactDescriptor): void { + if (artifact.familyId.equals(RECEIPT_FAMILY_ID)) { + return; + } + throw new WarpError('Continuum receipt projection requires receipt-family artifact authority', 'E_VALIDATION'); +} + +/** Freezes projected receipts. */ +function freezeReceipts(receipts: readonly ContinuumReceipt[]): readonly ContinuumReceipt[] { + return Object.freeze(receipts.slice()); +} diff --git a/src/domain/continuum/ContinuumReceiptProjector.ts b/src/domain/continuum/ContinuumReceiptProjector.ts new file mode 100644 index 00000000..dc0ba53a --- /dev/null +++ b/src/domain/continuum/ContinuumReceiptProjector.ts @@ -0,0 +1,59 @@ +import type ContinuumArtifactDescriptor from './ContinuumArtifactDescriptor.ts'; +import type ContinuumEvidenceStatus from './ContinuumEvidenceStatus.ts'; +import ContinuumReceipt from './ContinuumReceipt.ts'; +import ContinuumReceiptFamilyProjection from './ContinuumReceiptFamilyProjection.ts'; +import type { TickReceipt } from '../types/TickReceipt.ts'; + +const RESULT_SUPERSEDED = 'superseded'; + +export type ContinuumReceiptProjectionRequest = { + readonly artifact: ContinuumArtifactDescriptor; + readonly evidence: ContinuumEvidenceStatus; + readonly tickReceipts: readonly TickReceipt[]; +}; + +/** Projects git-warp tick receipts into Continuum receipt-family facts. */ +export default class ContinuumReceiptProjector { + /** Projects one git-warp tick receipt into the Continuum `Receipt` shape. */ + projectTickReceipt(receipt: TickReceipt): ContinuumReceipt { + const rejectedCount = countRejected(receipt); + const admittedCount = receipt.ops.length - rejectedCount; + return new ContinuumReceipt({ + receiptId: `git-warp:receipt:${receipt.patchSha}`, + headId: receipt.patchSha, + frameIndex: receipt.lamport, + laneId: receipt.writer, + writerId: receipt.writer, + inputTick: previousTick(receipt.lamport), + outputTick: receipt.lamport, + admittedRewriteCount: admittedCount, + rejectedRewriteCount: rejectedCount, + counterfactualCount: 0, + digest: receipt.patchSha, + summary: `${admittedCount} admitted, ${rejectedCount} rejected over ${receipt.ops.length} operation(s)`, + }); + } + + /** Projects a receipt-family fact set with artifact and evidence posture. */ + projectTickReceipts(request: ContinuumReceiptProjectionRequest): ContinuumReceiptFamilyProjection { + const receipts = request.tickReceipts.map((receipt) => this.projectTickReceipt(receipt)); + return new ContinuumReceiptFamilyProjection({ + artifact: request.artifact, + evidence: request.evidence, + receipts, + }); + } +} + +/** Counts operations that were rejected by CRDT admission. */ +function countRejected(receipt: TickReceipt): number { + return receipt.ops.filter((op) => op.result === RESULT_SUPERSEDED).length; +} + +/** Returns the previous non-negative tick. */ +function previousTick(tick: number): number { + if (tick === 0) { + return 0; + } + return tick - 1; +} diff --git a/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts b/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts new file mode 100644 index 00000000..d218cfa3 --- /dev/null +++ b/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it } from 'vitest'; +import ContinuumArtifactDescriptor from '../../../../src/domain/continuum/ContinuumArtifactDescriptor.ts'; +import ContinuumEvidenceStatus from '../../../../src/domain/continuum/ContinuumEvidenceStatus.ts'; +import ContinuumFamilyId from '../../../../src/domain/continuum/ContinuumFamilyId.ts'; +import ContinuumReceipt from '../../../../src/domain/continuum/ContinuumReceipt.ts'; +import ContinuumReceiptProjector from '../../../../src/domain/continuum/ContinuumReceiptProjector.ts'; +import { createTickReceipt, type TickReceipt } from '../../../../src/domain/types/TickReceipt.ts'; + +const PATCH_SHA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; +const SECOND_PATCH_SHA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; +const WRITER_ID = 'writer-a'; + +function createReceiptDescriptor(): ContinuumArtifactDescriptor { + return new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + sourceSchemaPath: 'schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley witness-continuum --scope receipt-family', + artifactKind: 'continuum.family.fixture', + authority: 'generated-fixture', + targets: ['warp-ttd', 'typescript'], + }); +} + +function createSettlementDescriptor(): ContinuumArtifactDescriptor { + return new ContinuumArtifactDescriptor({ + familyId: 'settlement-family', + sourceSchemaPath: 'schemas/continuum-settlement-family.graphql', + generatedBy: 'wesley witness-continuum --scope settlement-family', + artifactKind: 'continuum.family.fixture', + authority: 'generated-fixture', + targets: ['warp-ttd', 'typescript'], + }); +} + +function createReceipt(patchSha = PATCH_SHA, lamport = 7): TickReceipt { + return createTickReceipt({ + patchSha, + writer: WRITER_ID, + lamport, + ops: [ + { op: 'NodeAdd', target: 'node:a', result: 'applied' }, + { op: 'NodePropSet', target: 'node:a', result: 'redundant' }, + { op: 'EdgeAdd', target: 'node:a\0node:b\0rel', result: 'superseded' }, + ], + }); +} + +function translatedEvidence(): ContinuumEvidenceStatus { + return ContinuumEvidenceStatus.translatedGitWarp({ + basisRef: PATCH_SHA, + summary: 'git-warp tick receipt projected into receipt-family shape', + }); +} + +describe('ContinuumReceiptProjector', () => { + it('maps TickReceipt into the Continuum receipt-family Receipt shape', () => { + const receipt = new ContinuumReceiptProjector().projectTickReceipt(createReceipt()); + + expect(receipt).toBeInstanceOf(ContinuumReceipt); + expect(receipt.receiptId).toBe(`git-warp:receipt:${PATCH_SHA}`); + expect(receipt.headId).toBe(PATCH_SHA); + expect(receipt.frameIndex).toBe(7); + expect(receipt.laneId).toBe(WRITER_ID); + expect(receipt.writerId).toBe(WRITER_ID); + expect(receipt.inputTick).toBe(6); + expect(receipt.outputTick).toBe(7); + expect(receipt.admittedRewriteCount).toBe(2); + expect(receipt.rejectedRewriteCount).toBe(1); + expect(receipt.counterfactualCount).toBe(0); + expect(receipt.digest).toBe(PATCH_SHA); + expect(receipt.summary).toContain('2 admitted'); + expect(Object.isFrozen(receipt)).toBe(true); + }); + + it('wraps projected receipts with generated artifact authority and evidence status', () => { + const artifact = createReceiptDescriptor(); + const evidence = translatedEvidence(); + const projection = new ContinuumReceiptProjector().projectTickReceipts({ + artifact, + evidence, + tickReceipts: [ + createReceipt(PATCH_SHA, 7), + createReceipt(SECOND_PATCH_SHA, 8), + ], + }); + + expect(projection.artifact).toBe(artifact); + expect(projection.evidence).toBe(evidence); + expect(projection.artifact.familyId.equals(new ContinuumFamilyId('receipt-family'))).toBe(true); + expect(projection.evidence.isTranslatedSubstrate()).toBe(true); + expect(projection.receipts).toHaveLength(2); + expect(projection.receiptsForHead(PATCH_SHA)).toHaveLength(1); + expect(projection.receiptsForHead(SECOND_PATCH_SHA, 8)).toHaveLength(1); + expect(projection.receiptsForHead(SECOND_PATCH_SHA, 7)).toHaveLength(0); + }); + + it('rejects non-receipt-family artifacts', () => { + expect(() => new ContinuumReceiptProjector().projectTickReceipts({ + artifact: createSettlementDescriptor(), + evidence: translatedEvidence(), + tickReceipts: [createReceipt()], + })).toThrow('receipt-family'); + }); +}); diff --git a/test/unit/domain/index.exports.test.ts b/test/unit/domain/index.exports.test.ts index 0ae15e54..ead79557 100644 --- a/test/unit/domain/index.exports.test.ts +++ b/test/unit/domain/index.exports.test.ts @@ -64,6 +64,9 @@ import WarpAppDefault, { ContinuumEvidencePosture, ContinuumEvidenceStatus, ContinuumFamilyId, + ContinuumReceipt, + ContinuumReceiptFamilyProjection, + ContinuumReceiptProjector, ContinuumArtifactJsonFileAdapter, } from '../../../index.ts'; @@ -267,6 +270,9 @@ describe('index.ts exports', () => { expect(ContinuumEvidencePosture).toBeDefined(); expect(ContinuumEvidenceStatus).toBeDefined(); expect(ContinuumFamilyId).toBeDefined(); + expect(ContinuumReceipt).toBeDefined(); + expect(ContinuumReceiptFamilyProjection).toBeDefined(); + expect(ContinuumReceiptProjector).toBeDefined(); expect(ContinuumArtifactJsonFileAdapter).toBeDefined(); }); From e8fc2b1bf673951d03414152ce75958e880f076b Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 00:14:25 -0700 Subject: [PATCH 5/7] test: smoke warp ttd receipt projection --- docs/BEARING.md | 9 +- .../v18-warp-ttd-receipt-smoke.md | 136 ++++++++++++++++++ .../v18-warp-ttd-receipt-smoke.md | 52 +++++++ .../WarpTtdReceiptFamilySmoke.test.ts | 71 +++++++++ 4 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md create mode 100644 docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md create mode 100644 test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts diff --git a/docs/BEARING.md b/docs/BEARING.md index c5d7176b..1902f894 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -203,8 +203,13 @@ before the final commit for that slice, and mark completed items with `- [x]`. facts, `ContinuumReceiptFamilyProjection` carries the receipt-family artifact descriptor and explicit evidence status, and non-receipt-family artifacts are rejected. -- [ ] 10. Add the first `warp-ttd` smoke over generated-family git-warp receipt - facts instead of handwritten adapter-local receipt folklore. +- [x] 10. Add the first `warp-ttd` smoke over generated-family git-warp receipt + facts instead of handwritten adapter-local receipt folklore. The smoke starts + from a real committed git-warp patch, materializes real `TickReceipt` output, + loads the generated receipt-family fixture descriptor through the adapter + seam, projects the receipt into `ContinuumReceiptFamilyProjection`, queries + by head and frame for the `warp-ttd` target, and keeps evidence posture + explicitly translated rather than native. - [ ] 11. Re-plan with evidence in hand before expanding into reading-envelope, suffix/runtime-boundary, neighborhood-core, and settlement-family slices. diff --git a/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md b/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md new file mode 100644 index 00000000..38e75ad7 --- /dev/null +++ b/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md @@ -0,0 +1,136 @@ +--- +cycle: 0154 +task_id: V18_warp_ttd_receipt_smoke +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 WARP TTD Receipt Smoke + +## Pull + +`git-warp` can project `TickReceipt` into Continuum receipt-family facts. The +opening campaign needs one smoke test proving a `warp-ttd` consumer can read +those facts without reverse-engineering raw `TickReceipt` shape. + +## Hill + +A live git-warp patch receipt can be projected through the generated +receipt-family descriptor into `warp-ttd`-targeted receipt facts with explicit +translated evidence posture. + +## Playback Questions + +Agent: + +- Does the smoke start from a real committed git-warp patch? +- Does it load the generated receipt-family fixture descriptor instead of a + handwritten descriptor-only shortcut? +- Does it query projected receipt-family facts by head and frame for a + `warp-ttd` target? +- Does the projection keep translated evidence posture explicit? + +Human: + +- Is this enough proof to stop `warp-ttd` from depending on raw git-warp + `TickReceipt` folklore for the first receipt shell? + +## Accessibility / Assistive Reading Posture + +No visual surface changes. The smoke output is structured test evidence. + +## Localization / Directionality Posture + +No localized strings are introduced. + +## Agent Inspectability / Explainability Posture + +The smoke keeps artifact descriptor, evidence status, and projected receipts as +separate inspectable facts. + +## Non-Goals + +- Do not edit the `warp-ttd` repo in this slice. +- Do not add delivery observation projection. +- Do not claim native Continuum witnesshood. + +## RED + +Expected failing spec: + +```text +npx vitest run test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts +``` + +Observed result: + +```text +Test Files 1 passed (1) +Tests 1 passed (1) +``` + +This smoke became green immediately because slice 9 had already added the +receipt-family projection surface. + +## GREEN + +The smoke: + +1. opens a real in-memory git-warp runtime; +2. commits a real patch; +3. materializes real `TickReceipt` output; +4. loads the generated receipt-family fixture descriptor through + `ContinuumArtifactJsonFileAdapter`; +5. projects the materialized receipts into `ContinuumReceiptFamilyProjection`; +6. queries `receiptsForHead()` for the winning patch SHA and frame; +7. asserts the evidence posture remains translated substrate evidence, not + native Continuum evidence. + +## Playback + +Witness: + +```text +npx vitest run test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts test/unit/domain/continuum/ContinuumReceiptProjection.test.ts test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts +Test Files 4 passed (4) +Tests 24 passed (24) + +npm run typecheck:test -- --pretty false +npx eslint --no-warn-ignored test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts +``` + +Agent answers: + +- Yes, the smoke starts from a real committed git-warp patch. +- Yes, it loads the generated receipt-family fixture descriptor through the + artifact adapter. +- Yes, it queries projected receipt-family facts by head and frame for a + `warp-ttd` target. +- Yes, translated evidence posture remains explicit. + +Human answer: + +- This is enough first proof to stop `warp-ttd` from needing raw git-warp + `TickReceipt` folklore for the first receipt shell. + +## SSJS Scorecard + +- Runtime-backed forms: green; the smoke uses the runtime-backed projection + classes from slice 9. +- Boundary validation: green; generated fixture JSON is admitted through the + adapter seam. +- Behavior ownership: green; `git-warp` owns its receipt projection and + `warp-ttd` remains a consumer target. +- Message parsing: green; no behavior branches parse messages. +- Ambient time or entropy: green; no ambient time or entropy introduced. +- Fake shape trust or cast-cosplay: green; the projection remains translated + evidence and does not claim native witnesshood. + +## Closeout + +This closes BEARING task 10 and completes the requested five-slice batch. diff --git a/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md b/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md new file mode 100644 index 00000000..8dab9b67 --- /dev/null +++ b/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md @@ -0,0 +1,52 @@ +--- +cycle: 0154 +task_id: V18_warp_ttd_receipt_smoke +status: Complete +sponsors: + human: James + agent: Codex +completed_at: 2026-05-21 +--- + +# Retro: V18 WARP TTD Receipt Smoke + +## Hill + +A live git-warp patch receipt can be projected through the generated +receipt-family descriptor into `warp-ttd`-targeted receipt facts with explicit +translated evidence posture. + +## Result + +Hill met. + +## Witness + +```text +npx vitest run test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts test/unit/domain/continuum/ContinuumReceiptProjection.test.ts test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts +Test Files 4 passed (4) +Tests 24 passed (24) + +npm run typecheck:test -- --pretty false +npx eslint --no-warn-ignored test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts +``` + +## Drift Check + +No drift. The `warp-ttd` repo was inspected for consumer posture but not edited. +This slice stayed inside `git-warp`. + +## What Mess We Got Into + +The existing stack had enough local receipt truth, but `warp-ttd` could only +consume it by knowing the raw git-warp receipt shape. + +## What Mess We Got Out Of + +There is now an executable smoke proving a live git-warp receipt can be exposed +as generated receipt-family facts with translated evidence posture. + +## What Comes Next + +Re-plan with evidence in hand before expanding into reading envelopes, suffix +runtime boundaries, neighborhood core, and settlement-family cuts. diff --git a/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts b/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts new file mode 100644 index 00000000..8b8ff85f --- /dev/null +++ b/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest'; +import { fileURLToPath } from 'node:url'; + +import InMemoryGraphAdapter from '../../../../src/infrastructure/adapters/InMemoryGraphAdapter.ts'; +import ContinuumArtifactJsonFileAdapter, { + type ContinuumArtifactJsonLoadContext, +} from '../../../../src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; +import ContinuumEvidenceStatus from '../../../../src/domain/continuum/ContinuumEvidenceStatus.ts'; +import ContinuumReceiptProjector from '../../../../src/domain/continuum/ContinuumReceiptProjector.ts'; +import { openRuntimeHostProduct } from '../../../../src/domain/warp/RuntimeHostProduct.ts'; + +const GRAPH_NAME = 'warp-ttd-receipt-smoke'; +const WRITER_ID = 'writer-a'; +const NODE_ID = 'node:warp-ttd'; + +const generatedFixturePath = fileURLToPath( + new URL('../../../fixtures/continuum/receipt-family-generated-artifact.json', import.meta.url), +); + +const generatedFixtureContext: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'generated-fixture', + sourceSchemaPath: 'schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley witness-continuum --scope receipt-family', + witnessScope: 'receipt-family', + targets: ['warp-ttd', 'typescript'], +}; + +describe('warp-ttd receipt-family smoke', () => { + it('reads live git-warp receipts as generated-family facts with translated evidence', async () => { + const persistence = new InMemoryGraphAdapter(); + const graph = await openRuntimeHostProduct({ + persistence, + graphName: GRAPH_NAME, + writerId: WRITER_ID, + autoMaterialize: true, + }); + const artifact = await new ContinuumArtifactJsonFileAdapter().loadFile( + generatedFixturePath, + generatedFixtureContext, + ); + expect(artifact.hasTarget('warp-ttd')).toBe(true); + + const patchSha = await graph.patch((patch) => { + patch.addNode(NODE_ID).setProperty(NODE_ID, 'role', 'debug-target'); + }); + const materialized = await graph.materialize({ receipts: true }); + const evidence = ContinuumEvidenceStatus.translatedGitWarp({ + basisRef: patchSha, + summary: 'live git-warp receipt exposed as generated receipt-family facts for warp-ttd', + }); + const projection = new ContinuumReceiptProjector().projectTickReceipts({ + artifact, + evidence, + tickReceipts: materialized.receipts, + }); + + const receiptFacts = projection.receiptsForHead(patchSha, 1); + + expect(projection.evidence.isTranslatedSubstrate()).toBe(true); + expect(projection.evidence.isContinuumNative()).toBe(false); + expect(receiptFacts).toHaveLength(1); + expect(receiptFacts[0]?.headId).toBe(patchSha); + expect(receiptFacts[0]?.laneId).toBe(WRITER_ID); + expect(receiptFacts[0]?.writerId).toBe(WRITER_ID); + expect(receiptFacts[0]?.frameIndex).toBe(1); + expect(receiptFacts[0]?.outputTick).toBe(1); + expect(receiptFacts[0]?.admittedRewriteCount).toBeGreaterThan(0); + expect(receiptFacts[0]?.digest).toBe(patchSha); + }); +}); From 50284403576d037698df0a1edca39264eee6c56e Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 00:30:25 -0700 Subject: [PATCH 6/7] test: align mocks with writer CAS visibility --- .../detachedReadBenchmark.fixture.ts | 11 ++++++ test/helpers/WarpGraphMockPersistence.ts | 20 ++++++++--- .../WarpCore.snapshotHashStability.test.ts | 7 ++++ test/unit/domain/WarpGraph.conflicts.test.ts | 7 ++++ .../domain/WarpGraph.invalidation.test.ts | 6 ++-- .../domain/WarpGraph.observerBoundary.test.ts | 7 ++++ test/unit/domain/WarpGraph.patchCount.test.ts | 20 ++--------- test/unit/domain/WarpGraph.strands.test.ts | 7 ++++ test/unit/domain/WarpGraph.test.ts | 31 +++++++++------- test/unit/domain/WarpGraph.worldline.test.ts | 7 ++++ .../WarpGraph.writerInvalidation.test.ts | 6 ++-- .../services/PatchBuilder.content.test.ts | 16 +++++++-- .../unit/domain/services/PatchBuilder.test.ts | 27 ++++++++++---- test/unit/domain/warp/Writer.test.ts | 36 ++++++++++--------- 14 files changed, 143 insertions(+), 65 deletions(-) diff --git a/test/benchmark/detachedReadBenchmark.fixture.ts b/test/benchmark/detachedReadBenchmark.fixture.ts index 1ddeabd5..d5b5dcd5 100644 --- a/test/benchmark/detachedReadBenchmark.fixture.ts +++ b/test/benchmark/detachedReadBenchmark.fixture.ts @@ -61,6 +61,17 @@ function createMockPersistence() { updateRef: async (/** @type {string} */ ref, /** @type {string} */ sha) => { refs.set(ref, sha); }, + compareAndSwapRef: async ( + /** @type {string} */ ref, + /** @type {string} */ newOid, + /** @type {string | null} */ expectedOid, + ) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + }, deleteRef: async (/** @type {string} */ ref) => { refs.delete(ref); }, diff --git a/test/helpers/WarpGraphMockPersistence.ts b/test/helpers/WarpGraphMockPersistence.ts index eaa28770..3b6b519f 100644 --- a/test/helpers/WarpGraphMockPersistence.ts +++ b/test/helpers/WarpGraphMockPersistence.ts @@ -28,7 +28,8 @@ class MockPersistenceFixtureError extends Error { } class WarpGraphMockPersistence { - readonly readRef = vi.fn(); + readonly #refs = new Map(); + readonly readRef = vi.fn(async (ref: string) => this.#refs.get(ref) ?? null); readonly showNode = vi.fn(); readonly writeBlob = vi.fn(); readonly writeTree = vi.fn(); @@ -36,7 +37,9 @@ class WarpGraphMockPersistence { readonly readTreeOids = vi.fn().mockResolvedValue({}); readonly commitNode = vi.fn(); readonly commitNodeWithTree = vi.fn(); - readonly updateRef = vi.fn(); + readonly updateRef = vi.fn(async (ref: string, sha: string) => { + this.#refs.set(ref, sha); + }); readonly listRefs = vi.fn().mockResolvedValue([]); readonly getNodeInfo = vi.fn(); readonly ping = vi.fn().mockResolvedValue({ ok: true, latencyMs: 1 }); @@ -49,8 +52,17 @@ class WarpGraphMockPersistence { readonly countNodes = vi.fn().mockResolvedValue(0); readonly getCommitTree = vi.fn(); readonly readTree = vi.fn().mockResolvedValue({}); - readonly deleteRef = vi.fn(); - readonly compareAndSwapRef = vi.fn(); + readonly deleteRef = vi.fn(async (ref: string) => { + this.#refs.delete(ref); + }); + readonly compareAndSwapRef = vi.fn(async (ref: string, newOid: string, expectedOid: string | null) => { + const current = this.#refs.get(ref) ?? null; + if (current !== expectedOid) { + throw new MockPersistenceFixtureError(`CAS mismatch on ${ref}`); + } + this.#refs.set(ref, newOid); + this.readRef.mockImplementation(async (nextRef: string) => this.#refs.get(nextRef) ?? null); + }); get emptyTree(): string { return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; diff --git a/test/unit/domain/WarpCore.snapshotHashStability.test.ts b/test/unit/domain/WarpCore.snapshotHashStability.test.ts index c34faae3..0e6575ec 100644 --- a/test/unit/domain/WarpCore.snapshotHashStability.test.ts +++ b/test/unit/domain/WarpCore.snapshotHashStability.test.ts @@ -50,6 +50,13 @@ function createMockPersistence() { updateRef: vi.fn(async (ref, sha) => { refs.set(ref, sha); }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + }), deleteRef: vi.fn(async (ref) => { refs.delete(ref); }), diff --git a/test/unit/domain/WarpGraph.conflicts.test.ts b/test/unit/domain/WarpGraph.conflicts.test.ts index a8c0f54b..a7d4a228 100644 --- a/test/unit/domain/WarpGraph.conflicts.test.ts +++ b/test/unit/domain/WarpGraph.conflicts.test.ts @@ -44,6 +44,13 @@ function createMockPersistence() { updateRef: vi.fn(async (ref, sha) => { refs.set(ref, sha); }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + }), configGet: vi.fn(async () => null), configSet: vi.fn(async () => {}), showNode: vi.fn(async (sha) => { diff --git a/test/unit/domain/WarpGraph.invalidation.test.ts b/test/unit/domain/WarpGraph.invalidation.test.ts index 2bdef033..431b93fe 100644 --- a/test/unit/domain/WarpGraph.invalidation.test.ts +++ b/test/unit/domain/WarpGraph.invalidation.test.ts @@ -159,15 +159,15 @@ describe('WarpCore dirty flag + eager re-materialize (AP/INVAL/1 + AP/INVAL/2)', expect((graph)._stateDirty).toBe(false); }); - it('_stateDirty remains false if updateRef fails', async () => { + it('_stateDirty remains false if compareAndSwapRef fails', async () => { persistence.readRef.mockResolvedValue(null); persistence.writeBlob.mockResolvedValue(FAKE_BLOB_OID); persistence.writeTree.mockResolvedValue(FAKE_TREE_OID); persistence.commitNodeWithTree.mockResolvedValue(FAKE_COMMIT_SHA); - persistence.updateRef.mockRejectedValue(new Error('ref lock failed')); + persistence.compareAndSwapRef.mockRejectedValue(new Error('ref lock failed')); const patch = (await graph.createPatch()).addNode('test:node'); - await expect(patch.commit()).rejects.toThrow('ref lock failed'); + await expect(patch.commit()).rejects.toThrow('Commit failed: writer ref was updated by another process'); expect((graph)._stateDirty).toBe(false); }); diff --git a/test/unit/domain/WarpGraph.observerBoundary.test.ts b/test/unit/domain/WarpGraph.observerBoundary.test.ts index 0881d802..e1bb2869 100644 --- a/test/unit/domain/WarpGraph.observerBoundary.test.ts +++ b/test/unit/domain/WarpGraph.observerBoundary.test.ts @@ -51,6 +51,13 @@ function createMockPersistence() { updateRef: vi.fn(async (ref, sha) => { refs.set(ref, sha); }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + }), deleteRef: vi.fn(async (ref) => { refs.delete(ref); }), diff --git a/test/unit/domain/WarpGraph.patchCount.test.ts b/test/unit/domain/WarpGraph.patchCount.test.ts index b10d0d77..d182e527 100644 --- a/test/unit/domain/WarpGraph.patchCount.test.ts +++ b/test/unit/domain/WarpGraph.patchCount.test.ts @@ -112,15 +112,7 @@ describe('AP/CKPT/2: _patchesSinceCheckpoint tracking', () => { const tipSha = buildPatchChain(persistence, 'w1', patchCount); // checkpoint ref returns null (no checkpoint) - persistence.readRef.mockImplementation((/** @type {any} */ ref) => { - if (ref === 'refs/warp/test/checkpoints/head') { - return Promise.resolve(null); - } - if (ref === 'refs/warp/test/writers/w1') { - return Promise.resolve(tipSha); - } - return Promise.resolve(null); - }); + await persistence.updateRef('refs/warp/test/writers/w1', tipSha); // discoverWriters needs listRefs to return the writer ref persistence.listRefs.mockResolvedValue([ @@ -188,15 +180,7 @@ describe('AP/CKPT/2: _patchesSinceCheckpoint tracking', () => { const tipSha = buildPatchChain(persistence, 'w1', patchCount); // Phase 1: materialize with 3 patches (no checkpoint) - persistence.readRef.mockImplementation((/** @type {any} */ ref) => { - if (ref === 'refs/warp/test/checkpoints/head') { - return Promise.resolve(null); - } - if (ref === 'refs/warp/test/writers/w1') { - return Promise.resolve(tipSha); - } - return Promise.resolve(null); - }); + await persistence.updateRef('refs/warp/test/writers/w1', tipSha); persistence.listRefs.mockResolvedValue([ 'refs/warp/test/writers/w1', ]); diff --git a/test/unit/domain/WarpGraph.strands.test.ts b/test/unit/domain/WarpGraph.strands.test.ts index 2055e9be..ae83aab2 100644 --- a/test/unit/domain/WarpGraph.strands.test.ts +++ b/test/unit/domain/WarpGraph.strands.test.ts @@ -51,6 +51,13 @@ function createMockPersistence() { updateRef: vi.fn(async (ref, sha) => { refs.set(ref, sha); }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + }), deleteRef: vi.fn(async (ref) => { refs.delete(ref); }), diff --git a/test/unit/domain/WarpGraph.test.ts b/test/unit/domain/WarpGraph.test.ts index 4a097253..dfc341cb 100644 --- a/test/unit/domain/WarpGraph.test.ts +++ b/test/unit/domain/WarpGraph.test.ts @@ -40,8 +40,10 @@ function installCleanCheckpointReadingBasis( * @returns {any} Mock persistence adapter */ function createMockPersistence(): any { + const refs = new Map(); + const readRef = vi.fn(async (ref: string) => refs.get(ref) || null); return { - readRef: vi.fn(), + readRef, showNode: vi.fn(), writeBlob: vi.fn(), writeTree: vi.fn(), @@ -49,7 +51,17 @@ function createMockPersistence(): any { readTreeOids: vi.fn(), commitNode: vi.fn(), commitNodeWithTree: vi.fn(), - updateRef: vi.fn(), + updateRef: vi.fn(async (ref: string, sha: string) => { + refs.set(ref, sha); + }), + compareAndSwapRef: vi.fn(async (ref: string, newOid: string, expectedOid: string | null) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + readRef.mockImplementation(async (nextRef: string) => refs.get(nextRef) || null); + }), listRefs: vi.fn().mockResolvedValue([]), getNodeInfo: vi.fn(), ping: vi.fn().mockResolvedValue({ ok: true, latencyMs: 1 }), @@ -328,20 +340,19 @@ describe('WarpCore', () => { } as any); // Set up mock responses for commit - persistence.readRef.mockResolvedValue(null); persistence.writeBlob.mockResolvedValue('a'.repeat(40)); persistence.writeTree.mockResolvedValue('a'.repeat(40)); persistence.commitNodeWithTree.mockResolvedValue('a'.repeat(40)); - persistence.updateRef.mockResolvedValue(undefined); const patchBuilder = await graph.createPatch(); patchBuilder.addNode('test'); await patchBuilder.commit(); - // Verify the ref was updated with correct graph/writer path - expect(persistence.updateRef).toHaveBeenCalledWith( + // Verify the writer ref was advanced through CAS with correct graph/writer path + expect(persistence.compareAndSwapRef).toHaveBeenCalledWith( 'refs/warp/my-events/writers/writer-42', - expect.any(String) + expect.any(String), + null, ); }); @@ -1917,11 +1928,7 @@ eg-schema: 2`; const existingSha = 'd'.repeat(40); const existingPatchOid = 'e'.repeat(40); - persistence.readRef.mockImplementation((/** @type {any} */ ref) => { - if (ref.includes('checkpoints')) return Promise.resolve(null); - if (ref.includes('writers')) return Promise.resolve(existingSha); - return Promise.resolve(null); - }); + await persistence.updateRef('refs/warp/events/writers/writer-1', existingSha); persistence.listRefs.mockResolvedValue([]); persistence.showNode.mockResolvedValue( `warp:patch\n\neg-kind: patch\neg-graph: events\neg-writer: writer-1\neg-lamport: 5\neg-patch-oid: ${existingPatchOid}\neg-schema: 2` diff --git a/test/unit/domain/WarpGraph.worldline.test.ts b/test/unit/domain/WarpGraph.worldline.test.ts index ebc2c047..9cdea7b0 100644 --- a/test/unit/domain/WarpGraph.worldline.test.ts +++ b/test/unit/domain/WarpGraph.worldline.test.ts @@ -46,6 +46,13 @@ function createMockPersistence() { updateRef: vi.fn(async (ref, sha) => { refs.set(ref, sha); }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + }), deleteRef: vi.fn(async (ref) => { refs.delete(ref); }), diff --git a/test/unit/domain/WarpGraph.writerInvalidation.test.ts b/test/unit/domain/WarpGraph.writerInvalidation.test.ts index a85eab36..f339eef1 100644 --- a/test/unit/domain/WarpGraph.writerInvalidation.test.ts +++ b/test/unit/domain/WarpGraph.writerInvalidation.test.ts @@ -200,10 +200,12 @@ describe('WarpCore Writer invalidation (AP/INVAL/3)', () => { persistence.writeBlob.mockResolvedValue(FAKE_BLOB_OID); persistence.writeTree.mockResolvedValue(FAKE_TREE_OID); persistence.commitNodeWithTree.mockResolvedValue(FAKE_COMMIT_SHA); - persistence.updateRef.mockRejectedValue(new Error('ref lock failed')); + persistence.compareAndSwapRef.mockRejectedValue(new Error('ref lock failed')); const writer = await graph.writer('writer-1'); - await expect(writer.commitPatch((/** @type {any} */ p) => p.addNode('test:node'))).rejects.toThrow('ref lock failed'); + await expect(writer.commitPatch((/** @type {any} */ p) => p.addNode('test:node'))).rejects.toThrow( + 'Writer ref refs/warp/test/writers/writer-1 has advanced since beginPatch()', + ); expect((graph)._stateDirty).toBe(false); expect((graph)._cachedState).toBe(stateBeforeAttempt); diff --git a/test/unit/domain/services/PatchBuilder.content.test.ts b/test/unit/domain/services/PatchBuilder.content.test.ts index 3185577c..df80e4ef 100644 --- a/test/unit/domain/services/PatchBuilder.content.test.ts +++ b/test/unit/domain/services/PatchBuilder.content.test.ts @@ -28,13 +28,25 @@ function createMockBlobStorage(opts: { storeOid?: string } = {}) { * @returns {any} */ function createMockPersistence(overrides = {}) { + const refs = new Map(); + const readRef = vi.fn(async (ref) => refs.get(ref) || null); return { - readRef: vi.fn().mockResolvedValue(null), + readRef, showNode: vi.fn(), writeBlob: vi.fn().mockResolvedValue('d'.repeat(40)), writeTree: vi.fn().mockResolvedValue('e'.repeat(40)), commitNodeWithTree: vi.fn().mockResolvedValue('f'.repeat(40)), - updateRef: vi.fn().mockResolvedValue(undefined), + updateRef: vi.fn(async (ref, sha) => { + refs.set(ref, sha); + }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + readRef.mockImplementation(async (nextRef) => refs.get(nextRef) || null); + }), ...overrides, }; } diff --git a/test/unit/domain/services/PatchBuilder.test.ts b/test/unit/domain/services/PatchBuilder.test.ts index c1530cbe..a305874e 100644 --- a/test/unit/domain/services/PatchBuilder.test.ts +++ b/test/unit/domain/services/PatchBuilder.test.ts @@ -28,13 +28,25 @@ function createMockState() { * @returns {any} Mock persistence with standard methods stubbed */ function createMockPersistence() { + const refs = new Map(); + const readRef = vi.fn(async (ref) => refs.get(ref) || null); return { - readRef: vi.fn().mockResolvedValue(null), + readRef, showNode: vi.fn(), writeBlob: vi.fn().mockResolvedValue('a'.repeat(40)), // Valid 40-char hex OID writeTree: vi.fn().mockResolvedValue('b'.repeat(40)), commitNodeWithTree: vi.fn().mockResolvedValue('c'.repeat(40)), - updateRef: vi.fn().mockResolvedValue(undefined), + updateRef: vi.fn(async (ref, sha) => { + refs.set(ref, sha); + }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + readRef.mockImplementation(async (nextRef) => refs.get(nextRef) || null); + }), }; } @@ -589,9 +601,10 @@ describe('PatchBuilder', () => { expect(persistence.writeBlob).toHaveBeenCalledOnce(); expect(persistence.writeTree).toHaveBeenCalledOnce(); expect(persistence.commitNodeWithTree).toHaveBeenCalledOnce(); - expect(persistence.updateRef).toHaveBeenCalledWith( + expect(persistence.compareAndSwapRef).toHaveBeenCalledWith( 'refs/warp/test-graph/writers/writer1', - 'c'.repeat(40) + 'c'.repeat(40), + null, ); }); @@ -639,7 +652,7 @@ describe('PatchBuilder', () => { const existingSha = 'd'.repeat(40); const existingPatchOid = 'e'.repeat(40); // Simulate existing ref with lamport 5 - persistence.readRef.mockResolvedValue(existingSha); + await persistence.updateRef('refs/warp/test-graph/writers/writer1', existingSha); persistence.showNode.mockResolvedValue( `warp:patch\n\neg-kind: patch\neg-graph: test-graph\neg-writer: writer1\neg-lamport: 5\neg-patch-oid: ${existingPatchOid}\neg-schema: 2` ); @@ -866,7 +879,7 @@ describe('PatchBuilder', () => { it('does NOT set _committed on failed commit (mock persistence to throw)', async () => { const persistence = createMockPersistence(); - persistence.updateRef.mockRejectedValueOnce(new Error('simulated updateRef failure')); + persistence.compareAndSwapRef.mockRejectedValueOnce(new Error('simulated compareAndSwapRef failure')); const builder = new PatchBuilder(({ persistence, patchJournal: createPatchJournal(persistence), @@ -878,7 +891,7 @@ describe('PatchBuilder', () => { } as any)); builder.addNode('x'); - await expect(builder.commit()).rejects.toThrow('simulated updateRef failure'); + await expect(builder.commit()).rejects.toThrow('Commit failed: writer ref was updated by another process'); expect((builder as any)._committed).toBe(false); expect((builder as any)._committing).toBe(false); }); diff --git a/test/unit/domain/warp/Writer.test.ts b/test/unit/domain/warp/Writer.test.ts index aa09b765..acac4284 100644 --- a/test/unit/domain/warp/Writer.test.ts +++ b/test/unit/domain/warp/Writer.test.ts @@ -21,9 +21,21 @@ import { CborCodec } from '../../../../src/infrastructure/codecs/CborCodec.ts'; * Creates a minimal mock persistence adapter. */ function createMockPersistence() { + const refs = new Map(); + const readRef = vi.fn(async (ref) => refs.get(ref) || null); return { - readRef: vi.fn(), - updateRef: vi.fn(), + readRef, + updateRef: vi.fn(async (ref, sha) => { + refs.set(ref, sha); + }), + compareAndSwapRef: vi.fn(async (ref, newOid, expectedOid) => { + const current = refs.get(ref) || null; + if (current !== expectedOid) { + throw new Error(`CAS mismatch on ${ref}`); + } + refs.set(ref, newOid); + readRef.mockImplementation(async (nextRef) => refs.get(nextRef) || null); + }), showNode: vi.fn(), getNodeInfo: vi.fn(), writeBlob: vi.fn(), @@ -276,7 +288,7 @@ describe('Writer (WARP schema:2)', () => { const oldHead = 'a'.repeat(40); const newSha = 'b'.repeat(40); - persistence.readRef.mockResolvedValue(oldHead); + await persistence.updateRef(buildWriterRef('events', 'alice'), oldHead); persistence.showNode.mockResolvedValue(createPatchMessage(5)); persistence.writeBlob.mockResolvedValue('c'.repeat(40)); persistence.writeTree.mockResolvedValue('d'.repeat(40)); @@ -357,9 +369,10 @@ describe('Writer (WARP schema:2)', () => { patch.addNode('x'); await patch.commit(); - expect(persistence.updateRef).toHaveBeenCalledWith( + expect(persistence.compareAndSwapRef).toHaveBeenCalledWith( 'refs/warp/events/writers/alice', - newSha + newSha, + null, ); }); @@ -445,20 +458,9 @@ describe('Writer (WARP schema:2)', () => { const newSha2 = 'c'.repeat(40); // Setup: both patches see same head at begin time + await persistence.updateRef(buildWriterRef('events', 'alice'), oldHead); persistence.showNode.mockResolvedValue(createPatchMessage(5)); - // Sequence of readRef calls: - // 1. p1 beginPatch -> oldHead - // 2. p2 beginPatch -> oldHead - // 3. p1 commit PatchBuilder CAS check -> oldHead - // 4. (updateRef happens, ref is now newSha1) - // 5. p2 commit PatchBuilder CAS check -> newSha1 (fails here) - persistence.readRef - .mockResolvedValueOnce(oldHead) // p1 beginPatch - .mockResolvedValueOnce(oldHead) // p2 beginPatch - .mockResolvedValueOnce(oldHead) // p1 commit PatchBuilder - .mockResolvedValueOnce(newSha1); // p2 commit PatchBuilder (fails) - persistence.writeBlob.mockResolvedValue('d'.repeat(40)); persistence.writeTree.mockResolvedValue('e'.repeat(40)); persistence.commitNodeWithTree From 81cd83205f1a79929a61eb57277d70fce2086ce6 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 22 May 2026 03:53:06 -0700 Subject: [PATCH 7/7] fix: align continuum evidence posture --- CHANGELOG.md | 8 +++ docs/BEARING.md | 32 ++++++------ .../v18-continuum-compatibility-charter.md | 17 +++--- .../v18-continuum-contract-matrix.md | 18 +++---- .../v18-warp-optic-realization-map.md | 13 ++--- .../v18-evidence-posture.md | 52 ++++++++++--------- .../v18-receipt-family-projection.md | 10 ++-- .../v18-warp-ttd-receipt-smoke.md | 16 +++--- .../push-pr-review-merge.md | 2 +- .../v18-evidence-posture.md | 16 +++--- .../v18-same-writer-race-witness.md | 2 +- .../v18-receipt-family-projection.md | 6 +-- .../v18-warp-ttd-receipt-smoke.md | 4 +- .../continuum/ContinuumEvidencePosture.ts | 24 ++++----- .../continuum/ContinuumEvidenceStatus.ts | 42 +++++++-------- src/domain/warp/PatchSession.ts | 3 ++ .../continuum/ContinuumEvidenceStatus.test.ts | 46 ++++++++-------- .../ContinuumReceiptProjection.test.ts | 10 ++-- .../WarpTtdReceiptFamilySmoke.test.ts | 8 +-- test/unit/domain/index.exports.test.ts | 8 +-- .../PatchCommitter.visibility.test.ts | 17 ++++++ 21 files changed, 193 insertions(+), 161 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92803c09..b553cab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- v18 Continuum evidence posture now uses participant-runtime and + Continuum-witnessed vocabulary instead of substrate/native framing, and + git-warp participant evidence cannot carry a Continuum witness reference. +- Writer patch sessions now preserve `WRITER_COMMIT_NOT_VISIBLE` when a + post-CAS visibility failure reaches the public writer API instead of + collapsing it to `PERSIST_WRITE_FAILED`. +- BEARING and v18 cycle notes now reflect the current PR branch, latest closed + cycle, and participant evidence terminology. - Continuum artifact ingestion now enforces artifact-kind/authority pairing at the domain policy layer, keeps review fixtures repo-neutral, and splits the JSON adapter into focused parser, validation, fixture, and Wesley inventory diff --git a/docs/BEARING.md b/docs/BEARING.md index 1902f894..c6aca9ef 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -40,8 +40,8 @@ The long-term compatibility target is the WARP Optic shape described in `~/git/blog/aion-paper-07/dist/aion-paper-07.txt`, plus the Continuum contract families authored in `~/git/continuum/schemas/` and compiled by Wesley. Echo and `git-warp` are sibling runtime implementations. `git-warp` -has its own Continuum role, and it must not emit Continuum-shaped values as -native Continuum witnesses until that witnesshood is actually proven. +has its own Continuum role, and it must not attach a separate Continuum witness +reference to projected values until that witnesshood is actually proven. Backlog fold-in: the repo-visible v18 lane is `WL-4A-v18-graph-substrate-convergence` in @@ -56,12 +56,12 @@ Continuum role. Current branch state at this boundary: -- Branch: `v18-continuum-opening` +- Branch: `v18-evidence-receipt-projection` - Release tag: `v17.0.0` -- Latest remote head inspected: `origin/main` at `5afdd3eb` +- Latest remote head inspected: `origin/main` at `6d4a0d6` - Latest package version: `17.0.0` - Latest closed cycle: - `0145-push-pr-review-merge` + `0154-v18-warp-ttd-receipt-smoke` The release ladder is now: @@ -140,10 +140,10 @@ read-model groundwork, sync hardening, release gates, and package publishing. - v18 can easily turn into adapter folklore if `git-warp` hand-authors local mirrors of Continuum-owned families instead of consuming Wesley-generated artifacts. -- v18 can also lie in the other direction: Continuum-shaped values are not - Continuum-native witnesses unless the runtime has actually proven native - witnesshood. Initial git-warp compatibility evidence should be treated as - translated git-warp evidence until stronger proof exists. +- v18 can also lie in the other direction: Continuum-shaped values do not carry + separate Continuum witness references unless that witnesshood is actually + proven. Initial git-warp compatibility evidence should be treated as + participant-runtime evidence until stronger proof exists. - The v18 backlog already names a graph-model convergence lane. The plan must fold that lane into Continuum compatibility instead of replacing it with a parallel cross-repo adapter plan. @@ -178,13 +178,13 @@ before the final commit for that slice, and mark completed items with `- [x]`. and one-file-per-concept caps, self-attested authority fields from artifact JSON are rejected, policy-test authority fixtures are named constants, and empty or internally inconsistent Wesley generated inventory is rejected. -- [x] 6. Make evidence posture explicit: translated git-warp evidence first, - native Continuum evidence only after native witnesshood is proven. The +- [x] 6. Make evidence posture explicit: git-warp participant evidence first, + Continuum-witnessed evidence only after a witness reference is present. The current seam adds runtime-backed `ContinuumEvidencePosture` and - `ContinuumEvidenceStatus`; translated git-warp evidence is explicit - `translated-substrate` evidence, native Continuum evidence cannot be - constructed without `nativeWitnessRef`, and translated evidence rejects native - witness references. + `ContinuumEvidenceStatus`; git-warp evidence is explicit + `participant-runtime` evidence, Continuum-witnessed evidence cannot be + constructed without `continuumWitnessRef`, and participant-runtime evidence + rejects Continuum witness references. - [x] 7. Prove the patch commit visibility contract: success means canonical writer-tip advancement and visible graph truth, not just object creation. The patch commit path now advances writer refs with `compareAndSwapRef()`, @@ -209,7 +209,7 @@ before the final commit for that slice, and mark completed items with `- [x]`. loads the generated receipt-family fixture descriptor through the adapter seam, projects the receipt into `ContinuumReceiptFamilyProjection`, queries by head and frame for the `warp-ttd` target, and keeps evidence posture - explicitly translated rather than native. + explicitly participant-runtime rather than Continuum-witnessed. - [ ] 11. Re-plan with evidence in hand before expanding into reading-envelope, suffix/runtime-boundary, neighborhood-core, and settlement-family slices. diff --git a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md index bc935fdb..bf5e954d 100644 --- a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md +++ b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md @@ -25,7 +25,8 @@ participant without collapsing it into Echo, Wesley, or `warp-ttd`. - it consumes Wesley-generated artifacts for Continuum-owned contract families; - it maps append-only Git history into honest WARP Optic evidence; - it exposes generated-family facts to `warp-ttd`; -- it separates translated git-warp evidence from native Continuum witnesshood. +- it separates git-warp participant evidence from separate Continuum + witnesshood. ## Source Artifacts @@ -100,13 +101,13 @@ defines `git-warp`'s participant obligations. ## Evidence Posture -The default posture for existing git-warp facts mapped into Continuum-family -shapes is translated evidence. A value may be Continuum-shaped without being -Continuum-native. +The default posture for existing git-warp facts projected into Continuum-family +shapes is participant-runtime evidence. A value may be Continuum-shaped without +carrying a separate Continuum witness reference. -`git-warp` may claim native Continuum witnesshood only after a runtime witness +`git-warp` may attach a Continuum witness reference only after a runtime witness proves the value was produced through the corresponding Continuum family -contract and not merely mapped from local git-warp facts. +contract and not merely projected from local git-warp facts. ## Non-Goals @@ -116,7 +117,7 @@ contract and not merely mapped from local git-warp facts. contracts. - Do not build a generic WARP Optic runtime before repeated concrete compatibility cuts justify it. -- Do not claim native Continuum witnesshood for translated git-warp evidence. +- Do not claim separate Continuum witnesshood for git-warp participant evidence. ## Acceptance @@ -144,7 +145,7 @@ The v18 opening campaign is on track when: - Message parsing: green; no behavior branches are introduced. - Ambient time or entropy: green; no runtime code introduced. - Fake shape trust or cast-cosplay: green; the charter explicitly rejects fake - native witnesshood. + witnesshood. ## Closeout diff --git a/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md b/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md index 5f68a4f7..17cd73a1 100644 --- a/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md +++ b/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md @@ -26,8 +26,8 @@ contract families and the current proof gap for each one. - Echo and `git-warp` are sibling runtimes that may emit or consume conforming values. - `warp-ttd` is the structured debugger/read-model consumer. -- Existing `git-warp` facts are translated evidence until native Continuum - witnesshood is proven. +- Existing `git-warp` facts are participant-runtime evidence until separate + Continuum witnesshood is proven. ## Evidence Snapshot @@ -45,23 +45,23 @@ The matrix below is based on these inspected local sources: | Family | Authored home | Wesley status | `git-warp` source facts | Primary `warp-ttd` need | Missing witness | | --- | --- | --- | --- | --- | --- | -| `receipt-family` | `~/git/continuum/schemas/continuum-receipt-family.graphql` | `profiled`, `fixture-witnessed`; scope `receipt-family` checks cross-leg schema hash, TTD fixture shape, Echo fixture shape, boundary fixture, roundtrip vectors, and receipt/witness separation | `TickReceipt`, op outcomes, `DeliveryObservation`, audit receipt chains, materialize/provenance receipt collection | Receipt and delivery facts as generated-family nouns, not adapter-local summaries | Live `git-warp` receipt publication mapped through generated artifacts with translated evidence posture, then witnessed as native only after a Continuum runtime witness exists | +| `receipt-family` | `~/git/continuum/schemas/continuum-receipt-family.graphql` | `profiled`, `fixture-witnessed`; scope `receipt-family` checks cross-leg schema hash, TTD fixture shape, Echo fixture shape, boundary fixture, roundtrip vectors, and receipt/witness separation | `TickReceipt`, op outcomes, `DeliveryObservation`, audit receipt chains, materialize/provenance receipt collection | Receipt and delivery facts as generated-family nouns, not adapter-local summaries | Live `git-warp` receipt publication projected through generated artifacts with participant-runtime evidence posture, then linked to separate Continuum witnesses only when such witnesses exist | | `settlement-family` | `~/git/continuum/schemas/continuum-settlement-family.graphql` | `profiled`, `fixture-witnessed`; scope `settlement-family` checks cross-leg coherence and settlement boundary fixtures | Patch diffs, conflict traces, merge/conflict analysis, strand/braid conflict artifacts, writer frontier state | Import/settlement explanation for cross-runtime history and merge inspection | Live settlement values from `git-warp` suffix/import or merge flows, plus generated-artifact conformance | -| `neighborhood-core-family` | `~/git/continuum/schemas/continuum-neighborhood-core-family.graphql` | `authored`; not yet profiled in the current Continuum Wesley scope list | Graph name, writer refs, worldline/frontier facts, local site-like participation facts still unnamed as a stable family | Neighborhood focus, participant catalog, and site navigation across Echo and `git-warp` targets | Wesley profile and fixture witness first; then `git-warp` participant values with explicit translated/native evidence status | +| `neighborhood-core-family` | `~/git/continuum/schemas/continuum-neighborhood-core-family.graphql` | `authored`; not yet profiled in the current Continuum Wesley scope list | Graph name, writer refs, worldline/frontier facts, local site-like participation facts still unnamed as a stable family | Neighborhood focus, participant catalog, and site navigation across Echo and `git-warp` targets | Wesley profile and fixture witness first; then `git-warp` participant values with explicit participant-runtime or Continuum-witnessed evidence status | | `runtime-boundary-family` | `~/git/continuum/schemas/continuum-runtime-boundary-family.graphql` | `authored`; not yet profiled in the current Continuum Wesley scope list | Materialize/read requests, observer/read basis, patch suffixes, frontiers, provenance refs, receipt collections, import outcomes still split across local APIs | Admission-chain read model: observer plans, reading envelopes, evidence posture, suffix shells, causal suffix bundles, import outcomes | Wesley profile, generated fixtures, and a live witnessed suffix exchange/admission proof between sibling runtimes | ## Source-Fact Map | Continuum noun | Current `git-warp` anchor | Current posture | | --- | --- | --- | -| `Receipt` | `TickReceipt`, audit receipts, receipt shards | Translated evidence; shape is not yet generated-family native | +| `Receipt` | `TickReceipt`, audit receipts, receipt shards | Participant-runtime evidence; shape is not yet linked to separate Continuum witness refs | | `DeliveryObservation` | `DeliveryObservation` and effect sink observations | Local fact with strong name overlap; not yet Continuum family output | | `Witness` | checkpoint-tail witnesses, conflict witnesses, audit chain proofs | Local witness forms; no shared generated family surface yet | | `SettlementDelta` / `ConflictArtifact` | `PatchDiff`, conflict traces, merge/conflict services | Candidate source facts; missing shared generated settlement adapter | | `NeighborhoodCore` / `NeighborhoodParticipant` | graph name, writers, frontiers, worldline metadata | Candidate source facts; missing stable local site/participant object | | `ObserverPlan` / `ObservationRequest` | query/read basis, materialize options, traversal context | Candidate source facts; missing generated runtime-boundary profile | | `ReadingEnvelope` | materialize/query/read results plus provenance/receipt options | Candidate source facts; missing explicit evidence status wrapper | -| `TranslatedSubstrateEvidence` | append-only Git-backed causal history, patch SHAs, writer refs, receipts | Correct initial evidence posture for compatibility outputs | +| `ParticipantRuntimeEvidence` | append-only Git-backed causal history, patch SHAs, writer refs, receipts | Correct initial evidence posture for compatibility outputs | | `WitnessedSuffixShell` / `CausalSuffixBundle` | writer patch chains, frontier maps, transport/sync suffixes | Candidate source facts; missing compact generated shell and admission witness | | `ImportOutcome` | sync/import/materialization outcomes and conflict posture | Candidate source facts; missing runtime-boundary family emission | @@ -88,8 +88,8 @@ Recommended order: 1. Ingest or locally fixture the `receipt-family` generated artifact manifest. 2. Reject local `git-warp` files that claim to be authoritative mirrors of Continuum-owned families. -3. Map `TickReceipt` and `DeliveryObservation` into a translated - `receipt-family` projection without claiming native Continuum witnesshood. +3. Map `TickReceipt` and `DeliveryObservation` into a participant-runtime + `receipt-family` projection without claiming separate Continuum witnesshood. 4. Let `warp-ttd` consume that projection as generated-family-shaped input. ## SSJS Scorecard @@ -103,7 +103,7 @@ Recommended order: - Message parsing: green; no behavior branches introduced. - Ambient time or entropy: green; no runtime code introduced. - Fake shape trust or cast-cosplay: green; every current `git-warp` mapping is - marked as translated evidence until a stronger witness exists. + marked as participant-runtime evidence until a stronger witness exists. ## Closeout diff --git a/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md b/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md index 6c859f4b..7c998198 100644 --- a/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md +++ b/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md @@ -43,7 +43,7 @@ as a compatibility map over existing repo facts. | Scale | Weave `P` | Frontier `F*` | Result `R` | Witness `W` | Retained shell `theta` | Current posture | | --- | --- | --- | --- | --- | --- | --- | -| Tick / patch | One patch or ordered patch sequence | Writer and graph frontier | State transition, `PatchDiff`, or op outcomes | `TickReceipt`, op outcome details | Patch commit, receipt, optional audit receipt | Real local runtime fact; not native Continuum receipt-family output yet | +| Tick / patch | One patch or ordered patch sequence | Writer and graph frontier | State transition, `PatchDiff`, or op outcomes | `TickReceipt`, op outcome details | Patch commit, receipt, optional audit receipt | Real local runtime fact; not linked to separate Continuum receipt-family witnesses yet | | Read / optic | Observer/read target plus basis | Live, coordinate, or strand source | Node/property/traversal/materialized reading | `ReadIdentity`, checkpoint-tail witnesses, failure cause | Read identity plus checkpoint/tail anchors | Real local read fact; missing generated `ReadingEnvelope` | | Provenance slice | Backward cone for a target | Patch graph reachable from target | Bounded reconstructed state and patch count | Causal patch list, optional receipts | Provenance payload and source SHAs | Real local source fact; missing Continuum evidence wrapper | | Strand / braid | Strand overlay or braided strand set | Parent frontier plus overlay heads | Materialized strand state or conflict trace | conflict receipts, conflict anchors, participant traces | strand descriptor, overlay patches, conflict analysis | Candidate settlement-family source facts | @@ -68,8 +68,8 @@ Current `git-warp` facts map into it conservatively: ## Evidence Posture -The first v18 compatibility layer must mark `git-warp` outputs as translated -evidence unless a native Continuum runtime witness exists. +The first v18 compatibility layer must mark `git-warp` outputs as +participant-runtime evidence unless a separate Continuum witness exists. That means: @@ -77,7 +77,7 @@ That means: - a conflict trace can be mapped toward `settlement-family`; - a read result can be mapped toward `runtime-boundary-family`; - a sync suffix can be mapped toward `WitnessedSuffixShell`; -- none of those mappings may claim native Continuum witnesshood by shape alone. +- none of those mappings may claim separate Continuum witnesshood by shape alone. ## Next Engineering Cut @@ -92,7 +92,7 @@ The seam should not: - parse arbitrary GraphQL in the domain; - generate types at runtime; - make `git-warp` the owner of Continuum family semantics; -- equate translated `git-warp` evidence with native Continuum witnesshood. +- equate git-warp participant evidence with separate Continuum witnesshood. ## SSJS Scorecard @@ -104,7 +104,8 @@ The seam should not: modules and their gaps are named. - Message parsing: green; no behavior branches introduced. - Ambient time or entropy: green; no runtime code introduced. -- Fake shape trust or cast-cosplay: green; translated evidence is explicit. +- Fake shape trust or cast-cosplay: green; participant-runtime evidence is + explicit. ## Closeout diff --git a/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md b/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md index 03438fa0..c93943b6 100644 --- a/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md +++ b/docs/design/0150-v18-evidence-posture/v18-evidence-posture.md @@ -15,28 +15,29 @@ release_home: v18.0.0 ## Pull The generated-artifact seam can now admit Continuum-family descriptors, but -the next compatibility cut must prevent a shaped value from pretending to be a -native Continuum witness. +the next compatibility cut must prevent a projected value from pretending to +carry a separate Continuum witness reference. ## Hill -`git-warp` has a runtime-backed evidence status that separates translated -substrate evidence from native Continuum evidence and requires a native witness -reference before native evidence can be claimed. +`git-warp` has a runtime-backed evidence status that separates participant +runtime evidence from Continuum-witnessed evidence and requires an explicit +Continuum witness reference before witnessed evidence can be claimed. ## Playback Questions Agent: -- Does translated git-warp evidence carry an explicit translated posture? -- Does native Continuum evidence require a native witness reference? -- Does the model reject translated evidence that tries to smuggle in a native - witness reference? +- Does git-warp participant evidence carry an explicit participant-runtime + posture? +- Does Continuum-witnessed evidence require a witness reference? +- Does the model reject participant-runtime evidence that tries to smuggle in a + Continuum witness reference? Human: -- Can later v18 receipt projections say "this is git-warp evidence translated - into a Continuum-family shape" without overclaiming? +- Can later v18 receipt projections say "this is git-warp participant evidence + projected into a Continuum-family shape" without overclaiming? ## Accessibility / Assistive Reading Posture @@ -51,11 +52,11 @@ summaries remain ordinary strings supplied by callers. ## Agent Inspectability / Explainability Posture The status object exposes posture, source runtime, basis reference, optional -native witness reference, and summary as inspectable fields. +Continuum witness reference, and summary as inspectable fields. ## Non-Goals -- Do not implement native Continuum witness production. +- Do not implement Continuum witness production. - Do not generate receipt-family values in this slice. - Do not build a generic WARP Optic engine. @@ -80,11 +81,11 @@ This slice adds: - `ContinuumEvidencePosture` - `ContinuumEvidenceStatus` -Translated git-warp evidence is represented as `translated-substrate` with -`sourceRuntime: "git-warp"`. Native Continuum evidence is represented as -`continuum-native` and cannot be constructed unless `nativeWitnessRef` is -present. Translated evidence rejects `nativeWitnessRef` so compatibility output -cannot smuggle native witnesshood through an optional field. +Git-warp participant evidence is represented as `participant-runtime` with +`sourceRuntime: "git-warp"`. Continuum-witnessed evidence is represented as +`continuum-witnessed` and cannot be constructed unless `continuumWitnessRef` is +present. Participant-runtime evidence rejects `continuumWitnessRef` so +compatibility output cannot smuggle witnesshood through an optional field. ## Playback @@ -100,14 +101,15 @@ npm run typecheck:src -- --pretty false Agent answers: -- Yes, translated git-warp evidence carries explicit translated posture. -- Yes, native Continuum evidence requires `nativeWitnessRef`. -- Yes, translated evidence with `nativeWitnessRef` is rejected. +- Yes, git-warp participant evidence carries explicit participant-runtime + posture. +- Yes, Continuum-witnessed evidence requires `continuumWitnessRef`. +- Yes, participant-runtime evidence with `continuumWitnessRef` is rejected. Human answer: -- Later receipt-family projections can carry translated git-warp evidence - without claiming native Continuum witnesshood. +- Later receipt-family projections can carry git-warp participant evidence + without claiming separate Continuum witnesshood. ## SSJS Scorecard @@ -118,8 +120,8 @@ Human answer: live on the evidence concepts. - Message parsing: green; no message parsing introduced. - Ambient time or entropy: green; no ambient time or entropy introduced. -- Fake shape trust or cast-cosplay: green; native evidence cannot be claimed - without an explicit native witness reference. +- Fake shape trust or cast-cosplay: green; Continuum-witnessed evidence cannot + be claimed without an explicit witness reference. ## Closeout diff --git a/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md b/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md index 59f14540..ae4e5d37 100644 --- a/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md +++ b/docs/design/0153-v18-receipt-family-projection/v18-receipt-family-projection.md @@ -15,14 +15,14 @@ release_home: v18.0.0 ## Pull The repo can admit generated receipt-family artifacts and can now distinguish -translated substrate evidence from native Continuum evidence. The next step is -to project real `git-warp` receipt facts into the generated Continuum +git-warp participant evidence from Continuum-witnessed evidence. The next step +is to project real `git-warp` receipt facts into the generated Continuum receipt-family shape. ## Hill `TickReceipt` values can be projected into Continuum receipt-family `Receipt` -facts with generated artifact authority and explicit translated git-warp +facts with generated artifact authority and explicit git-warp participant evidence posture. ## Playback Questions @@ -57,7 +57,7 @@ separate inspectable fields. ## Non-Goals -- Do not claim native Continuum witnesshood. +- Do not claim separate Continuum witnesshood. - Do not add delivery observation projection yet. - Do not call Wesley or parse GraphQL at runtime. @@ -134,7 +134,7 @@ Human answer: - Message parsing: green; no behavior branches parse messages. - Ambient time or entropy: green; no ambient time or entropy introduced. - Fake shape trust or cast-cosplay: green; evidence status remains separate and - translated by default. + participant-runtime by default. ## Closeout diff --git a/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md b/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md index 38e75ad7..ffcdd645 100644 --- a/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md +++ b/docs/design/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md @@ -22,7 +22,7 @@ those facts without reverse-engineering raw `TickReceipt` shape. A live git-warp patch receipt can be projected through the generated receipt-family descriptor into `warp-ttd`-targeted receipt facts with explicit -translated evidence posture. +participant-runtime evidence posture. ## Playback Questions @@ -33,7 +33,7 @@ Agent: handwritten descriptor-only shortcut? - Does it query projected receipt-family facts by head and frame for a `warp-ttd` target? -- Does the projection keep translated evidence posture explicit? +- Does the projection keep participant-runtime evidence posture explicit? Human: @@ -57,7 +57,7 @@ separate inspectable facts. - Do not edit the `warp-ttd` repo in this slice. - Do not add delivery observation projection. -- Do not claim native Continuum witnesshood. +- Do not claim separate Continuum witnesshood. ## RED @@ -88,8 +88,8 @@ The smoke: `ContinuumArtifactJsonFileAdapter`; 5. projects the materialized receipts into `ContinuumReceiptFamilyProjection`; 6. queries `receiptsForHead()` for the winning patch SHA and frame; -7. asserts the evidence posture remains translated substrate evidence, not - native Continuum evidence. +7. asserts the evidence posture remains participant-runtime evidence, not + Continuum-witnessed evidence. ## Playback @@ -111,7 +111,7 @@ Agent answers: artifact adapter. - Yes, it queries projected receipt-family facts by head and frame for a `warp-ttd` target. -- Yes, translated evidence posture remains explicit. +- Yes, participant-runtime evidence posture remains explicit. Human answer: @@ -128,8 +128,8 @@ Human answer: `warp-ttd` remains a consumer target. - Message parsing: green; no behavior branches parse messages. - Ambient time or entropy: green; no ambient time or entropy introduced. -- Fake shape trust or cast-cosplay: green; the projection remains translated - evidence and does not claim native witnesshood. +- Fake shape trust or cast-cosplay: green; the projection remains + participant-runtime evidence and does not claim separate witnesshood. ## Closeout diff --git a/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md b/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md index 30e8df71..9704c686 100644 --- a/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md +++ b/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md @@ -35,4 +35,4 @@ acceptance over generated-family facts. The release train made it into the station, then the station sign kept saying "boarding soon." This retro fixes the sign. The next mess is bigger: teach `git-warp` to speak Continuum contract families without dressing adapter -folklore up as native witnesshood. +folklore up as separate Continuum witnesshood. diff --git a/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md b/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md index 2a620907..d4704d2f 100644 --- a/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md +++ b/docs/method/retro/0150-v18-evidence-posture/v18-evidence-posture.md @@ -12,9 +12,9 @@ completed_at: 2026-05-21 ## Hill -`git-warp` has a runtime-backed evidence status that separates translated -substrate evidence from native Continuum evidence and requires a native witness -reference before native evidence can be claimed. +`git-warp` has a runtime-backed evidence status that separates participant +runtime evidence from Continuum-witnessed evidence and requires an explicit +Continuum witness reference before witnessed evidence can be claimed. ## Result @@ -33,18 +33,18 @@ npm run typecheck:src -- --pretty false ## Drift Check No drift. The implementation stayed within the evidence-posture slice and did -not start receipt-family projection or native witness production. +not start receipt-family projection or Continuum witness production. ## What Mess We Got Into The repo had a generated-artifact gate but no runtime object for the more -dangerous claim: whether a Continuum-shaped value is native evidence or merely -translated substrate evidence. +dangerous claim: whether a Continuum-shaped value carries an explicit witness +reference or is participant-runtime evidence from `git-warp`. ## What Mess We Got Out Of -Native Continuum evidence now has to carry `nativeWitnessRef`. Translated -git-warp evidence is the explicit default and cannot include that native +Continuum-witnessed evidence now has to carry `continuumWitnessRef`. +Git-warp participant evidence is the explicit default and cannot include that witness field. ## What Comes Next diff --git a/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md b/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md index 8ae3731d..3ba51623 100644 --- a/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md +++ b/docs/method/retro/0152-v18-same-writer-race-witness/v18-same-writer-race-witness.md @@ -49,4 +49,4 @@ builder wins; the losing builder is not graph truth. ## What Comes Next Project `TickReceipt` facts into Continuum receipt-family `Receipt` facts with -translated git-warp evidence posture. +git-warp participant evidence posture. diff --git a/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md b/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md index da9b7518..0d6078a1 100644 --- a/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md +++ b/docs/method/retro/0153-v18-receipt-family-projection/v18-receipt-family-projection.md @@ -13,7 +13,7 @@ completed_at: 2026-05-21 ## Hill `TickReceipt` values can be projected into Continuum receipt-family `Receipt` -facts with generated artifact authority and explicit translated git-warp +facts with generated artifact authority and explicit git-warp participant evidence posture. ## Result @@ -37,7 +37,7 @@ git diff --check ## Drift Check No drift. The slice projected `TickReceipt` to receipt-family `Receipt` facts -only. Delivery observations and native Continuum witness production remain +only. Delivery observations and separate Continuum witness production remain out of scope. ## What Mess We Got Into @@ -47,7 +47,7 @@ shape. That is adapter folklore, not a generated-family contract. ## What Mess We Got Out Of -`git-warp` now owns the translation from its local receipt fact into a +`git-warp` now owns the projection from its local receipt fact into a Continuum receipt-family `Receipt`, with evidence posture carried separately. ## What Comes Next diff --git a/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md b/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md index 8dab9b67..882e3ee6 100644 --- a/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md +++ b/docs/method/retro/0154-v18-warp-ttd-receipt-smoke/v18-warp-ttd-receipt-smoke.md @@ -14,7 +14,7 @@ completed_at: 2026-05-21 A live git-warp patch receipt can be projected through the generated receipt-family descriptor into `warp-ttd`-targeted receipt facts with explicit -translated evidence posture. +participant-runtime evidence posture. ## Result @@ -44,7 +44,7 @@ consume it by knowing the raw git-warp receipt shape. ## What Mess We Got Out Of There is now an executable smoke proving a live git-warp receipt can be exposed -as generated receipt-family facts with translated evidence posture. +as generated receipt-family facts with participant-runtime evidence posture. ## What Comes Next diff --git a/src/domain/continuum/ContinuumEvidencePosture.ts b/src/domain/continuum/ContinuumEvidencePosture.ts index 2c433f34..15befd87 100644 --- a/src/domain/continuum/ContinuumEvidencePosture.ts +++ b/src/domain/continuum/ContinuumEvidencePosture.ts @@ -1,15 +1,15 @@ import WarpError from '../errors/WarpError.ts'; -const TRANSLATED_SUBSTRATE_POSTURE = 'translated-substrate'; -const CONTINUUM_NATIVE_POSTURE = 'continuum-native'; +const PARTICIPANT_RUNTIME_POSTURE = 'participant-runtime'; +const CONTINUUM_WITNESSED_POSTURE = 'continuum-witnessed'; export type ContinuumEvidencePostureValue = - | typeof TRANSLATED_SUBSTRATE_POSTURE - | typeof CONTINUUM_NATIVE_POSTURE; + | typeof PARTICIPANT_RUNTIME_POSTURE + | typeof CONTINUUM_WITNESSED_POSTURE; export const CONTINUUM_EVIDENCE_POSTURES: readonly ContinuumEvidencePostureValue[] = Object.freeze([ - TRANSLATED_SUBSTRATE_POSTURE, - CONTINUUM_NATIVE_POSTURE, + PARTICIPANT_RUNTIME_POSTURE, + CONTINUUM_WITNESSED_POSTURE, ]); /** Runtime-backed evidence posture for Continuum-compatible values. */ @@ -21,14 +21,14 @@ export default class ContinuumEvidencePosture { Object.freeze(this); } - /** Returns true for compatibility evidence translated from git-warp substrate facts. */ - isTranslatedSubstrate(): boolean { - return this.value === TRANSLATED_SUBSTRATE_POSTURE; + /** Returns true for evidence produced by a Continuum participant runtime. */ + isParticipantRuntime(): boolean { + return this.value === PARTICIPANT_RUNTIME_POSTURE; } - /** Returns true only for values backed by native Continuum witnesshood. */ - isContinuumNative(): boolean { - return this.value === CONTINUUM_NATIVE_POSTURE; + /** Returns true only for values backed by an explicit Continuum witness. */ + isContinuumWitnessed(): boolean { + return this.value === CONTINUUM_WITNESSED_POSTURE; } /** Returns the stable posture string. */ diff --git a/src/domain/continuum/ContinuumEvidenceStatus.ts b/src/domain/continuum/ContinuumEvidenceStatus.ts index 5999b563..ee8ae626 100644 --- a/src/domain/continuum/ContinuumEvidenceStatus.ts +++ b/src/domain/continuum/ContinuumEvidenceStatus.ts @@ -6,10 +6,10 @@ export type ContinuumEvidenceStatusFields = { readonly sourceRuntime: string; readonly basisRef: string; readonly summary: string; - readonly nativeWitnessRef?: string; + readonly continuumWitnessRef?: string; }; -export type TranslatedGitWarpEvidenceFields = { +export type GitWarpParticipantEvidenceFields = { readonly basisRef: string; readonly summary: string; }; @@ -20,36 +20,36 @@ export default class ContinuumEvidenceStatus { readonly sourceRuntime: string; readonly basisRef: string; readonly summary: string; - readonly nativeWitnessRef: string | undefined; + readonly continuumWitnessRef: string | undefined; constructor(fields: ContinuumEvidenceStatusFields) { this.posture = normalizePosture(fields.posture); this.sourceRuntime = requireNonEmptyString(fields.sourceRuntime, 'sourceRuntime'); this.basisRef = requireNonEmptyString(fields.basisRef, 'basisRef'); this.summary = requireNonEmptyString(fields.summary, 'summary'); - this.nativeWitnessRef = optionalNonEmptyString(fields.nativeWitnessRef, 'nativeWitnessRef'); - validateNativeWitnessPosture(this.posture, this.nativeWitnessRef); + this.continuumWitnessRef = optionalNonEmptyString(fields.continuumWitnessRef, 'continuumWitnessRef'); + validateContinuumWitnessPosture(this.posture, this.continuumWitnessRef); Object.freeze(this); } - /** Creates the default v18 evidence posture for git-warp compatibility output. */ - static translatedGitWarp(fields: TranslatedGitWarpEvidenceFields): ContinuumEvidenceStatus { + /** Creates the default v18 evidence posture for git-warp participant output. */ + static gitWarpParticipant(fields: GitWarpParticipantEvidenceFields): ContinuumEvidenceStatus { return new ContinuumEvidenceStatus({ - posture: 'translated-substrate', + posture: 'participant-runtime', sourceRuntime: 'git-warp', basisRef: fields.basisRef, summary: fields.summary, }); } - /** Returns true for compatibility evidence translated from substrate facts. */ - isTranslatedSubstrate(): boolean { - return this.posture.isTranslatedSubstrate(); + /** Returns true for evidence produced by a Continuum participant runtime. */ + isParticipantRuntime(): boolean { + return this.posture.isParticipantRuntime(); } - /** Returns true only when native Continuum witnesshood is explicitly carried. */ - isContinuumNative(): boolean { - return this.posture.isContinuumNative(); + /** Returns true only when an explicit Continuum witness reference is carried. */ + isContinuumWitnessed(): boolean { + return this.posture.isContinuumWitnessed(); } } @@ -77,15 +77,15 @@ function optionalNonEmptyString(value: string | undefined, name: string): string return requireNonEmptyString(value, name); } -/** Enforces that native evidence cannot be claimed by posture alone. */ -function validateNativeWitnessPosture( +/** Enforces that witnessed evidence cannot be claimed by posture alone. */ +function validateContinuumWitnessPosture( posture: ContinuumEvidencePosture, - nativeWitnessRef: string | undefined, + continuumWitnessRef: string | undefined, ): void { - if (posture.isContinuumNative() && nativeWitnessRef === undefined) { - throw new WarpError('nativeWitnessRef is required for native Continuum evidence', 'E_VALIDATION'); + if (posture.isContinuumWitnessed() && continuumWitnessRef === undefined) { + throw new WarpError('continuumWitnessRef is required for Continuum-witnessed evidence', 'E_VALIDATION'); } - if (posture.isTranslatedSubstrate() && nativeWitnessRef !== undefined) { - throw new WarpError('translated substrate evidence must not carry nativeWitnessRef', 'E_VALIDATION'); + if (posture.isParticipantRuntime() && continuumWitnessRef !== undefined) { + throw new WarpError('participant runtime evidence must not carry continuumWitnessRef', 'E_VALIDATION'); } } diff --git a/src/domain/warp/PatchSession.ts b/src/domain/warp/PatchSession.ts index d73c67ac..3f8121d3 100644 --- a/src/domain/warp/PatchSession.ts +++ b/src/domain/warp/PatchSession.ts @@ -74,6 +74,9 @@ function _buildCasConflictError( function _classifyCommitError(err: unknown, ctx: CommitContext): WriterError { // nosemgrep: ts-no-unknown-outside-adapters -- 0025B const { errMsg, cause } = _extractErrorInfo(err); const casError = _extractCasError(err); + if (casError !== null && casError.code === 'WRITER_COMMIT_NOT_VISIBLE') { + return new WriterError('WRITER_COMMIT_NOT_VISIBLE', errMsg, cause); + } if (casError !== null && casError.code === 'WRITER_CAS_CONFLICT') { return _buildCasConflictError(casError, cause, ctx); } diff --git a/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts b/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts index 91ab62b5..bd1e2c95 100644 --- a/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts +++ b/test/unit/domain/continuum/ContinuumEvidenceStatus.test.ts @@ -3,56 +3,56 @@ import ContinuumEvidencePosture from '../../../../src/domain/continuum/Continuum import ContinuumEvidenceStatus from '../../../../src/domain/continuum/ContinuumEvidenceStatus.ts'; const PATCH_BASIS_REF = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; -const NATIVE_WITNESS_REF = 'continuum:witness:receipt-family:1'; +const CONTINUUM_WITNESS_REF = 'continuum:witness:receipt-family:1'; describe('ContinuumEvidenceStatus', () => { - it('marks git-warp evidence as translated substrate evidence', () => { - const status = ContinuumEvidenceStatus.translatedGitWarp({ + it('marks git-warp evidence as participant runtime evidence', () => { + const status = ContinuumEvidenceStatus.gitWarpParticipant({ basisRef: PATCH_BASIS_REF, summary: 'git-warp patch receipt projected into receipt-family shape', }); - expect(status.posture.toString()).toBe('translated-substrate'); + expect(status.posture.toString()).toBe('participant-runtime'); expect(status.sourceRuntime).toBe('git-warp'); expect(status.basisRef).toBe(PATCH_BASIS_REF); - expect(status.nativeWitnessRef).toBeUndefined(); - expect(status.isTranslatedSubstrate()).toBe(true); - expect(status.isContinuumNative()).toBe(false); + expect(status.continuumWitnessRef).toBeUndefined(); + expect(status.isParticipantRuntime()).toBe(true); + expect(status.isContinuumWitnessed()).toBe(false); expect(Object.isFrozen(status)).toBe(true); }); - it('accepts native Continuum evidence only with an explicit native witness reference', () => { + it('accepts Continuum-witnessed evidence only with an explicit witness reference', () => { const status = new ContinuumEvidenceStatus({ - posture: 'continuum-native', + posture: 'continuum-witnessed', sourceRuntime: 'git-warp', basisRef: PATCH_BASIS_REF, - nativeWitnessRef: NATIVE_WITNESS_REF, - summary: 'receipt-family value was produced through native Continuum witnesshood', + continuumWitnessRef: CONTINUUM_WITNESS_REF, + summary: 'receipt-family value carries an explicit Continuum witness reference', }); - expect(status.posture.toString()).toBe('continuum-native'); - expect(status.nativeWitnessRef).toBe(NATIVE_WITNESS_REF); - expect(status.isContinuumNative()).toBe(true); - expect(status.isTranslatedSubstrate()).toBe(false); + expect(status.posture.toString()).toBe('continuum-witnessed'); + expect(status.continuumWitnessRef).toBe(CONTINUUM_WITNESS_REF); + expect(status.isContinuumWitnessed()).toBe(true); + expect(status.isParticipantRuntime()).toBe(false); }); - it('rejects native Continuum evidence without a native witness reference', () => { + it('rejects Continuum-witnessed evidence without a witness reference', () => { expect(() => new ContinuumEvidenceStatus({ - posture: 'continuum-native', + posture: 'continuum-witnessed', sourceRuntime: 'git-warp', basisRef: PATCH_BASIS_REF, summary: 'missing witness', - })).toThrow('nativeWitnessRef'); + })).toThrow('continuumWitnessRef'); }); - it('rejects translated substrate evidence that carries a native witness reference', () => { + it('rejects participant runtime evidence that carries a Continuum witness reference', () => { expect(() => new ContinuumEvidenceStatus({ - posture: 'translated-substrate', + posture: 'participant-runtime', sourceRuntime: 'git-warp', basisRef: PATCH_BASIS_REF, - nativeWitnessRef: NATIVE_WITNESS_REF, - summary: 'translated evidence cannot claim native witnesshood', - })).toThrow('translated substrate evidence must not carry nativeWitnessRef'); + continuumWitnessRef: CONTINUUM_WITNESS_REF, + summary: 'participant runtime evidence cannot claim a separate witness reference', + })).toThrow('participant runtime evidence must not carry continuumWitnessRef'); }); }); diff --git a/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts b/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts index d218cfa3..ad16fdad 100644 --- a/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts +++ b/test/unit/domain/continuum/ContinuumReceiptProjection.test.ts @@ -45,8 +45,8 @@ function createReceipt(patchSha = PATCH_SHA, lamport = 7): TickReceipt { }); } -function translatedEvidence(): ContinuumEvidenceStatus { - return ContinuumEvidenceStatus.translatedGitWarp({ +function participantEvidence(): ContinuumEvidenceStatus { + return ContinuumEvidenceStatus.gitWarpParticipant({ basisRef: PATCH_SHA, summary: 'git-warp tick receipt projected into receipt-family shape', }); @@ -74,7 +74,7 @@ describe('ContinuumReceiptProjector', () => { it('wraps projected receipts with generated artifact authority and evidence status', () => { const artifact = createReceiptDescriptor(); - const evidence = translatedEvidence(); + const evidence = participantEvidence(); const projection = new ContinuumReceiptProjector().projectTickReceipts({ artifact, evidence, @@ -87,7 +87,7 @@ describe('ContinuumReceiptProjector', () => { expect(projection.artifact).toBe(artifact); expect(projection.evidence).toBe(evidence); expect(projection.artifact.familyId.equals(new ContinuumFamilyId('receipt-family'))).toBe(true); - expect(projection.evidence.isTranslatedSubstrate()).toBe(true); + expect(projection.evidence.isParticipantRuntime()).toBe(true); expect(projection.receipts).toHaveLength(2); expect(projection.receiptsForHead(PATCH_SHA)).toHaveLength(1); expect(projection.receiptsForHead(SECOND_PATCH_SHA, 8)).toHaveLength(1); @@ -97,7 +97,7 @@ describe('ContinuumReceiptProjector', () => { it('rejects non-receipt-family artifacts', () => { expect(() => new ContinuumReceiptProjector().projectTickReceipts({ artifact: createSettlementDescriptor(), - evidence: translatedEvidence(), + evidence: participantEvidence(), tickReceipts: [createReceipt()], })).toThrow('receipt-family'); }); diff --git a/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts b/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts index 8b8ff85f..25103cab 100644 --- a/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts +++ b/test/unit/domain/continuum/WarpTtdReceiptFamilySmoke.test.ts @@ -27,7 +27,7 @@ const generatedFixtureContext: ContinuumArtifactJsonLoadContext = { }; describe('warp-ttd receipt-family smoke', () => { - it('reads live git-warp receipts as generated-family facts with translated evidence', async () => { + it('reads live git-warp receipts as generated-family facts with participant evidence', async () => { const persistence = new InMemoryGraphAdapter(); const graph = await openRuntimeHostProduct({ persistence, @@ -45,7 +45,7 @@ describe('warp-ttd receipt-family smoke', () => { patch.addNode(NODE_ID).setProperty(NODE_ID, 'role', 'debug-target'); }); const materialized = await graph.materialize({ receipts: true }); - const evidence = ContinuumEvidenceStatus.translatedGitWarp({ + const evidence = ContinuumEvidenceStatus.gitWarpParticipant({ basisRef: patchSha, summary: 'live git-warp receipt exposed as generated receipt-family facts for warp-ttd', }); @@ -57,8 +57,8 @@ describe('warp-ttd receipt-family smoke', () => { const receiptFacts = projection.receiptsForHead(patchSha, 1); - expect(projection.evidence.isTranslatedSubstrate()).toBe(true); - expect(projection.evidence.isContinuumNative()).toBe(false); + expect(projection.evidence.isParticipantRuntime()).toBe(true); + expect(projection.evidence.isContinuumWitnessed()).toBe(false); expect(receiptFacts).toHaveLength(1); expect(receiptFacts[0]?.headId).toBe(patchSha); expect(receiptFacts[0]?.laneId).toBe(WRITER_ID); diff --git a/test/unit/domain/index.exports.test.ts b/test/unit/domain/index.exports.test.ts index ead79557..05e709a8 100644 --- a/test/unit/domain/index.exports.test.ts +++ b/test/unit/domain/index.exports.test.ts @@ -291,14 +291,14 @@ describe('index.ts exports', () => { expect(descriptor.hasGeneratedAuthority()).toBe(true); }); - it('constructs translated git-warp evidence status from public exports', () => { - const status = ContinuumEvidenceStatus.translatedGitWarp({ + it('constructs git-warp participant evidence status from public exports', () => { + const status = ContinuumEvidenceStatus.gitWarpParticipant({ basisRef: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - summary: 'git-warp evidence translated into Continuum shape', + summary: 'git-warp participant evidence projected into Continuum shape', }); expect(status.posture).toBeInstanceOf(ContinuumEvidencePosture); - expect(status.isTranslatedSubstrate()).toBe(true); + expect(status.isParticipantRuntime()).toBe(true); }); }); diff --git a/test/unit/domain/services/PatchCommitter.visibility.test.ts b/test/unit/domain/services/PatchCommitter.visibility.test.ts index ec68da5a..7f56d1b3 100644 --- a/test/unit/domain/services/PatchCommitter.visibility.test.ts +++ b/test/unit/domain/services/PatchCommitter.visibility.test.ts @@ -90,6 +90,23 @@ describe('PatchCommitter visibility contract', () => { }); }); + it('preserves post-CAS visibility errors through Writer patch sessions', async () => { + const persistence = new DriftAfterCasGraphAdapter(); + const graph = await openRuntimeHostProduct({ + persistence, + graphName: GRAPH_NAME, + writerId: WRITER_ID, + autoMaterialize: true, + }); + const writer = await graph.writer(WRITER_ID); + + await expect(writer.commitPatch((patch) => { + patch.addNode('node:writer-drift'); + })).rejects.toMatchObject({ + code: 'WRITER_COMMIT_NOT_VISIBLE', + }); + }); + it('makes the returned patch commit visible through graph materialization', async () => { const persistence = new RecordingGraphAdapter(); const graph = await openRuntimeHostProduct({