v18 Continuum opening campaign#91
Conversation
📝 WalkthroughWalkthroughThis PR implements v18 Continuum artifact ingestion capability: design docs establish the v18 graph-model convergence strategy, domain models enforce artifact authority and descriptor validation, a JSON adapter parses Wesley manifests and Continuum fixtures safely, and public API exports complete the integration for artifact handling across the ecosystem. ChangesV18 Continuum Artifact Ingestion
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Release Preflight
If you tag this commit as |
|
@codex Self-review pass after clean-worktree check and Issue count: Critical 0, Major 3, Minor 2.
|
|
@coderabbitai review please |
🧩 Analysis chainI'll gather the relevant files before starting the full review. 🏁 Script executed: Length of output: 57081 🏁 Script executed: Length of output: 7949
Review: v18 Continuum Opening Campaign✅ All five self-review items confirmed🔴 Major 1 — Shape and authority are not bound togetherConfirmed. // ContinuumArtifactAuthority.ts
isGeneratedAuthority(): boolean {
return (
this.value === GENERATED_ARTIFACT_AUTHORITY || // 'generated-artifact'
this.value === GENERATED_FIXTURE_AUTHORITY // 'generated-fixture'
);
}
The intended invariant — that Wesley manifests carry 🔴 Major 2 — Caller context can override parsed artifact kindConfirmed. Both parsing branches delegate // wesleyManifestFields() [~line 131]
artifactKind: context.artifactKind ?? WESLEY_REALIZATION_MANIFEST_KIND,
// parseContinuumFamilyFixture() [~line 161]
artifactKind: context.artifactKind ?? CONTINUUM_FIXTURE_KIND,The parsed shape determines which branch runs, but the emitted descriptor's 🔴 Major 3 — Wesley generated inventory can be empty or internally inconsistentConfirmed, with a nuance. // readGeneratedLegs() [~line 258]
const names = Object.freeze(Object.keys(generatedLegs).sort()); // {} → []
// No minimum-leg check followsSeveral existing test fixtures that use The
🟡 Minor 4 — Machine-specific absolute path in fixtureConfirmed. "canonicalSchemaPath": "/Users/james/git/continuum/schemas/continuum-receipt-family.graphql"The field is 🟡 Minor 5 — Verification prose documents a known-nonzero exit commandConfirmed. The design doc at lines 109–115 states the targeted Additional observation —
|
| # | Severity | Status |
|---|---|---|
| 1 | Major | Confirmed — generated-artifact / generated-fixture are both admitted for any shape; policy has no shape-authority coupling |
| 2 | Major | Confirmed — context.artifactKind can produce a descriptor whose artifactKind contradicts the parsed JSON shape |
| 3 | Major | Confirmed — empty generatedLegs: {} passes silently; positive artifactCount is never cross-checked against actual files.length |
| 4 | Minor | Confirmed — absolute workstation path in shared fixture |
| 5 | Minor | Confirmed — nonzero-exit command in verification prose |
✅ Actions performed
Review triggered.
Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/domain/continuum/ContinuumArtifactIngestionPolicy.ts`:
- Around line 13-19: assertGeneratedAuthority currently only checks
descriptor.hasGeneratedAuthority() and therefore allows mismatched
kind/authority pairs; update it to validate that the descriptor's actual
authority exactly matches the expected generated authority for its shape/kind.
In practice, inside assertGeneratedAuthority use ContinuumArtifactDescriptor's
identifying property (e.g., kind or shape) to determine the expected generated
authority (compute or call a helper like
expectedGeneratedAuthorityForKind(kind)) and then check both
descriptor.hasGeneratedAuthority() and
descriptor.authority.equals(expectedAuthority); if either fails throw the
existing ContinuumArtifactAuthorityError with a message that includes
descriptor.familyId, descriptor.kind (or shape) and the actual vs expected
authority so mismatches like Wesley manifest + generated-fixture or fixture +
generated-artifact are rejected. Ensure you reference and update
assertGeneratedAuthority, ContinuumArtifactDescriptor, and the thrown
ContinuumArtifactAuthorityError only.
In `@src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts`:
- Around line 133-135: The parsed JSON must determine artifactKind and authority
rather than letting context override them: in ContinuumArtifactJsonFileAdapter's
JSON parsing logic (the block that sets generatedBy, artifactKind, targets using
readStringArray/readString), read the artifactKind and authority from the source
JSON shape and bind artifactKind to that value (do not default to
context.artifactKind), and validate that context.authority (if provided) matches
the parsed authority; if they mismatch, throw a validation/error. Apply the same
enforcement where similar parsing occurs (the other parsing blocks around the
indicated ranges 158-163 and 166-177).
- Around line 258-303: readGeneratedLegs currently accepts empty generatedLegs
and doesn't validate artifactCount against the actual files, allowing
inconsistent sealed manifests; update readGeneratedLegs (and readGeneratedFiles
usage) so that after calling readOptionalNumber(leg, 'artifactCount') and
collecting files via readGeneratedFiles(leg, name) you validate: if
artifactCount is present and not equal to files.length throw an
AdapterValidationError with a clear message referencing the leg name, and
additionally reject an entirely empty generatedLegs map by throwing an
AdapterValidationError when names.length === 0; use the existing
AdapterValidationError type and the functions readOptionalNumber,
readGeneratedFiles, and rejectUnknownKeys to locate where to insert these checks
in readGeneratedLegs.
In `@test/fixtures/continuum/receipt-family-wesley-realization-manifest.json`:
- Line 4: The fixture contains a machine-specific absolute "canonicalSchemaPath"
value which breaks portability; edit the
receipt-family-wesley-realization-manifest.json fixture to either remove the
optional "canonicalSchemaPath" field entirely or replace its value with a
repo-relative path (e.g., "./schemas/continuum-receipt-family.graphql") so the
fixture is consistent across contributors/CI and no host-specific absolute path
remains.
In `@test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts`:
- Line 14: The test uses an environment-specific fixture value for
sourceSchemaPath ('~/git/continuum/...') which should be replaced with a
repo-neutral constant; update the test ContinuumArtifactIngestionPolicy.test.ts
to import or reference the shared test fixture constant (e.g.,
SCHEMA_FIXTURE_PATH or a suite-level RELATIVE_SCHEMA_PATH) and replace all
occurrences of sourceSchemaPath in the file (including the other instances
noted) with that constant so the path is repo-relative and portable across
environments.
In `@test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts`:
- Around line 276-349: Add three regression tests to the
ContinuumArtifactJsonFileAdapter test suite: (1) create a fixture JSON loaded
via ContinuumArtifactJsonFileAdapter.loadString with authority
'generated-artifact' and attempt to load a Wesley manifest JSON with authority
'generated-fixture' using loadString, asserting it throws AdapterValidationError
(shape-binding/authority mismatch); (2) add a test that constructs a sealed
Wesley manifest where manifest.artifactCount !== manifest.files.length and
assert ContinuumArtifactJsonFileAdapter.loadString throws AdapterValidationError
(ensure explicit artifactCount must match files.length); and (3) add a test that
creates a sealed Wesley manifest with generatedLegs = [] and assert loadString
throws AdapterValidationError (empty generatedLegs rejected for sealed
manifests). Use existing fixture JSON constants and the
ContinuumArtifactJsonFileAdapter class and its loadString/loadFile calls to
locate where to add these tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 47103933-1949-48c5-89f1-9178d67e2899
⛔ Files ignored due to path filters (4)
docs/design/0124-v17-release-blocker-dag.dotis excluded by!**/*.dotdocs/design/0124-v17-release-blocker-dag.svgis excluded by!**/*.svgdocs/design/0124-v17-release-blocker-status.csvis excluded by!**/*.csvdocs/design/continuum-categories.pdfis excluded by!**/*.pdf
📒 Files selected for processing (30)
docs/BEARING.mddocs/VISION.mddocs/design/0124-v17-release-blocker-dag.mddocs/design/0145-push-pr-review-merge/push-pr-review-merge.mddocs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.mddocs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.mddocs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.mddocs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.mddocs/design/continuum-categories.texdocs/method/backlog/README.mddocs/method/backlog/WORKLOADS.mddocs/method/backlog/bad-code/RELEASE_TRIAGE.mddocs/method/backlog/v18.0.0/PROTO_echo-shaped-edge-records.mddocs/method/backlog/v18.0.0/PROTO_echo-shaped-node-records.mddocs/method/backlog/v18.0.0/README.mddocs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.mdindex.tssrc/domain/continuum/ContinuumArtifactAuthority.tssrc/domain/continuum/ContinuumArtifactDescriptor.tssrc/domain/continuum/ContinuumArtifactIngestionPolicy.tssrc/domain/continuum/ContinuumFamilyId.tssrc/domain/errors/ContinuumArtifactAuthorityError.tssrc/domain/errors/index.tssrc/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.tstest/fixtures/continuum/receipt-family-generated-artifact.jsontest/fixtures/continuum/receipt-family-wesley-realization-manifest.jsontest/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.tstest/unit/domain/errors/index.test.tstest/unit/domain/index.exports.test.tstest/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts
| assertGeneratedAuthority(descriptor: ContinuumArtifactDescriptor): void { | ||
| if (descriptor.hasGeneratedAuthority()) { | ||
| return; | ||
| } | ||
| throw new ContinuumArtifactAuthorityError( | ||
| `Continuum family ${descriptor.familyId.toString()} must be loaded from a generated artifact or fixture, not ${descriptor.authority.toString()}`, | ||
| ); |
There was a problem hiding this comment.
Authority validation is too coarse and permits kind/authority mismatches.
Current policy accepts any “generated” authority and does not enforce the expected authority for each descriptor shape/kind. This can admit contradictory descriptors (e.g., Wesley manifest + generated-fixture, Continuum fixture + generated-artifact).
💡 Proposed tightening
+const EXPECTED_AUTHORITY_BY_KIND = Object.freeze({
+ 'wesley.realization.manifest.v1': 'generated-artifact',
+ 'continuum.family.fixture': 'generated-fixture',
+} as const);
+
export default class ContinuumArtifactIngestionPolicy {
ingest(descriptor: ContinuumArtifactDescriptor): ContinuumArtifactDescriptor {
- this.assertGeneratedAuthority(descriptor);
+ this.assertGeneratedAuthority(descriptor);
return descriptor;
}
assertGeneratedAuthority(descriptor: ContinuumArtifactDescriptor): void {
- if (descriptor.hasGeneratedAuthority()) {
- return;
- }
- throw new ContinuumArtifactAuthorityError(
- `Continuum family ${descriptor.familyId.toString()} must be loaded from a generated artifact or fixture, not ${descriptor.authority.toString()}`,
- );
+ const expected = EXPECTED_AUTHORITY_BY_KIND[
+ descriptor.artifactKind as keyof typeof EXPECTED_AUTHORITY_BY_KIND
+ ];
+ if (expected === undefined || descriptor.authority.toString() !== expected) {
+ throw new ContinuumArtifactAuthorityError(
+ `Continuum family ${descriptor.familyId.toString()} requires authority ${expected ?? 'known-kind-authority'} for ${descriptor.artifactKind}, not ${descriptor.authority.toString()}`,
+ );
+ }
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/domain/continuum/ContinuumArtifactIngestionPolicy.ts` around lines 13 -
19, assertGeneratedAuthority currently only checks
descriptor.hasGeneratedAuthority() and therefore allows mismatched
kind/authority pairs; update it to validate that the descriptor's actual
authority exactly matches the expected generated authority for its shape/kind.
In practice, inside assertGeneratedAuthority use ContinuumArtifactDescriptor's
identifying property (e.g., kind or shape) to determine the expected generated
authority (compute or call a helper like
expectedGeneratedAuthorityForKind(kind)) and then check both
descriptor.hasGeneratedAuthority() and
descriptor.authority.equals(expectedAuthority); if either fails throw the
existing ContinuumArtifactAuthorityError with a message that includes
descriptor.familyId, descriptor.kind (or shape) and the actual vs expected
authority so mismatches like Wesley manifest + generated-fixture or fixture +
generated-artifact are rejected. Ensure you reference and update
assertGeneratedAuthority, ContinuumArtifactDescriptor, and the thrown
ContinuumArtifactAuthorityError only.
| generatedBy: context.generatedBy ?? 'wesley compile', | ||
| artifactKind: context.artifactKind ?? WESLEY_REALIZATION_MANIFEST_KIND, | ||
| targets: readStringArray(source, 'targets'), |
There was a problem hiding this comment.
Bind authority and artifactKind to the parsed JSON shape.
context.authority and optional context.artifactKind can currently contradict the parsed payload shape, so callers can emit mismatched descriptors. Enforce shape-derived artifactKind and reject authority mismatches per shape.
Suggested fix
export type ContinuumArtifactJsonLoadContext = {
readonly familyId: string | ContinuumFamilyId;
readonly authority: string | ContinuumArtifactAuthority;
readonly sourceSchemaPath?: string;
readonly generatedBy?: string;
- readonly artifactKind?: string;
readonly version?: string;
readonly targets?: readonly string[];
readonly witnessScope?: string;
readonly artifactDigest?: string;
};
@@
function parseWesleyRealizationManifest(
source: JsonObject,
context: ContinuumArtifactJsonLoadContext,
): ContinuumArtifactDescriptorFields {
+ assertExpectedAuthority(context.authority, 'generated-artifact', 'Wesley realization manifest');
validateWesleyManifestEnvelope(source);
const integrity = readSealedIntegrity(source);
const legs = readGeneratedLegs(source);
return descriptorFields(context, {
- ...wesleyManifestFields(source, context),
+ ...wesleyManifestFields(source),
...integrity,
generatedLegs: legs.names,
generatedFiles: legs.files,
});
}
@@
-function wesleyManifestFields(
- source: JsonObject,
- context: ContinuumArtifactJsonLoadContext,
-): DescriptorFieldSource {
+function wesleyManifestFields(source: JsonObject): DescriptorFieldSource {
return {
sourceSchemaPath: readRequiredString(source, 'schemaPath'),
- generatedBy: context.generatedBy ?? 'wesley compile',
- artifactKind: context.artifactKind ?? WESLEY_REALIZATION_MANIFEST_KIND,
+ generatedBy: 'wesley compile',
+ artifactKind: WESLEY_REALIZATION_MANIFEST_KIND,
targets: readStringArray(source, 'targets'),
@@
function parseContinuumFamilyFixture(
source: JsonObject,
context: ContinuumArtifactJsonLoadContext,
): ContinuumArtifactDescriptorFields {
+ assertExpectedAuthority(context.authority, 'generated-fixture', 'Continuum family fixture');
@@
return descriptorFields(context, {
sourceSchemaPath: readContextString(context.sourceSchemaPath, 'sourceSchemaPath'),
generatedBy: context.generatedBy ?? CONTINUUM_FIXTURE_GENERATOR,
- artifactKind: context.artifactKind ?? CONTINUUM_FIXTURE_KIND,
+ artifactKind: CONTINUUM_FIXTURE_KIND,
targets: context.targets ?? [CONTINUUM_FIXTURE_TARGET],
});
}Also applies to: 158-163, 166-177
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts` around lines
133 - 135, The parsed JSON must determine artifactKind and authority rather than
letting context override them: in ContinuumArtifactJsonFileAdapter's JSON
parsing logic (the block that sets generatedBy, artifactKind, targets using
readStringArray/readString), read the artifactKind and authority from the source
JSON shape and bind artifactKind to that value (do not default to
context.artifactKind), and validate that context.authority (if provided) matches
the parsed authority; if they mismatch, throw a validation/error. Apply the same
enforcement where similar parsing occurs (the other parsing blocks around the
indicated ranges 158-163 and 166-177).
| function readGeneratedLegs(source: JsonObject): { | ||
| readonly names: readonly string[]; | ||
| readonly files: readonly string[]; | ||
| } { | ||
| const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); | ||
| const names = Object.freeze(Object.keys(generatedLegs).sort()); | ||
| const files: string[] = []; | ||
| for (const name of names) { | ||
| const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); | ||
| rejectUnknownKeys( | ||
| leg, | ||
| ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], | ||
| `Wesley generated leg "${name}"`, | ||
| ); | ||
| readRequiredString(leg, 'outDir'); | ||
| readRequiredString(leg, 'schemaHash'); | ||
| readRequiredString(leg, 'sourceHash'); | ||
| readOptionalStringArray(leg, 'targets'); | ||
| readOptionalNumber(leg, 'artifactCount'); | ||
| for (const path of readGeneratedFiles(leg, name)) { | ||
| files.push(path); | ||
| } | ||
| } | ||
| return { names, files: Object.freeze(files.sort()) }; | ||
| } | ||
|
|
||
| /** Reads generated file entries from one Wesley generated leg. */ | ||
| function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { | ||
| const value = source['files']; | ||
| if (value === undefined) { | ||
| return []; | ||
| } | ||
| if (!Array.isArray(value)) { | ||
| throw new AdapterValidationError(`Wesley generated leg "${legName}" field "files" must be an array`); | ||
| } | ||
| const files: string[] = []; | ||
| for (const entry of value) { | ||
| const file = requireJsonObject(entry, `Wesley generated leg "${legName}" file`); | ||
| rejectUnknownKeys(file, ['path', 'size', 'contentHash', 'signature'], `Wesley generated leg "${legName}" file`); | ||
| files.push(readRequiredString(file, 'path')); | ||
| readRequiredNumber(file, 'size'); | ||
| readRequiredString(file, 'contentHash'); | ||
| readRequiredString(file, 'signature'); | ||
| } | ||
| return Object.freeze(files); | ||
| } |
There was a problem hiding this comment.
Reject empty/inconsistent generated-leg inventory in sealed Wesley manifests.
readGeneratedLegs accepts {} and never validates artifactCount against files.length, so sealed manifests can claim inventory that does not match file evidence.
Suggested fix
function readGeneratedLegs(source: JsonObject): {
readonly names: readonly string[];
readonly files: readonly string[];
} {
const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs');
const names = Object.freeze(Object.keys(generatedLegs).sort());
+ if (names.length === 0) {
+ throw new AdapterValidationError('Wesley realization manifest must include at least one generated leg');
+ }
const files: string[] = [];
for (const name of names) {
@@
- readOptionalNumber(leg, 'artifactCount');
- for (const path of readGeneratedFiles(leg, name)) {
+ const artifactCount = readOptionalNumber(leg, 'artifactCount');
+ const legFiles = readGeneratedFiles(leg, name);
+ if (artifactCount !== undefined && artifactCount !== legFiles.length) {
+ throw new AdapterValidationError(
+ `Wesley generated leg "${name}" artifactCount must equal files length`,
+ );
+ }
+ if (artifactCount !== undefined && artifactCount > 0 && legFiles.length === 0) {
+ throw new AdapterValidationError(
+ `Wesley generated leg "${name}" with positive artifactCount must include files`,
+ );
+ }
+ for (const path of legFiles) {
files.push(path);
}
}
return { names, files: Object.freeze(files.sort()) };
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function readGeneratedLegs(source: JsonObject): { | |
| readonly names: readonly string[]; | |
| readonly files: readonly string[]; | |
| } { | |
| const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); | |
| const names = Object.freeze(Object.keys(generatedLegs).sort()); | |
| const files: string[] = []; | |
| for (const name of names) { | |
| const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); | |
| rejectUnknownKeys( | |
| leg, | |
| ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], | |
| `Wesley generated leg "${name}"`, | |
| ); | |
| readRequiredString(leg, 'outDir'); | |
| readRequiredString(leg, 'schemaHash'); | |
| readRequiredString(leg, 'sourceHash'); | |
| readOptionalStringArray(leg, 'targets'); | |
| readOptionalNumber(leg, 'artifactCount'); | |
| for (const path of readGeneratedFiles(leg, name)) { | |
| files.push(path); | |
| } | |
| } | |
| return { names, files: Object.freeze(files.sort()) }; | |
| } | |
| /** Reads generated file entries from one Wesley generated leg. */ | |
| function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { | |
| const value = source['files']; | |
| if (value === undefined) { | |
| return []; | |
| } | |
| if (!Array.isArray(value)) { | |
| throw new AdapterValidationError(`Wesley generated leg "${legName}" field "files" must be an array`); | |
| } | |
| const files: string[] = []; | |
| for (const entry of value) { | |
| const file = requireJsonObject(entry, `Wesley generated leg "${legName}" file`); | |
| rejectUnknownKeys(file, ['path', 'size', 'contentHash', 'signature'], `Wesley generated leg "${legName}" file`); | |
| files.push(readRequiredString(file, 'path')); | |
| readRequiredNumber(file, 'size'); | |
| readRequiredString(file, 'contentHash'); | |
| readRequiredString(file, 'signature'); | |
| } | |
| return Object.freeze(files); | |
| } | |
| function readGeneratedLegs(source: JsonObject): { | |
| readonly names: readonly string[]; | |
| readonly files: readonly string[]; | |
| } { | |
| const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); | |
| const names = Object.freeze(Object.keys(generatedLegs).sort()); | |
| if (names.length === 0) { | |
| throw new AdapterValidationError('Wesley realization manifest must include at least one generated leg'); | |
| } | |
| const files: string[] = []; | |
| for (const name of names) { | |
| const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); | |
| rejectUnknownKeys( | |
| leg, | |
| ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], | |
| `Wesley generated leg "${name}"`, | |
| ); | |
| readRequiredString(leg, 'outDir'); | |
| readRequiredString(leg, 'schemaHash'); | |
| readRequiredString(leg, 'sourceHash'); | |
| readOptionalStringArray(leg, 'targets'); | |
| const artifactCount = readOptionalNumber(leg, 'artifactCount'); | |
| const legFiles = readGeneratedFiles(leg, name); | |
| if (artifactCount !== undefined && artifactCount !== legFiles.length) { | |
| throw new AdapterValidationError( | |
| `Wesley generated leg "${name}" artifactCount must equal files length`, | |
| ); | |
| } | |
| if (artifactCount !== undefined && artifactCount > 0 && legFiles.length === 0) { | |
| throw new AdapterValidationError( | |
| `Wesley generated leg "${name}" with positive artifactCount must include files`, | |
| ); | |
| } | |
| for (const path of legFiles) { | |
| files.push(path); | |
| } | |
| } | |
| return { names, files: Object.freeze(files.sort()) }; | |
| } | |
| /** Reads generated file entries from one Wesley generated leg. */ | |
| function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { | |
| const value = source['files']; | |
| if (value === undefined) { | |
| return []; | |
| } | |
| if (!Array.isArray(value)) { | |
| throw new AdapterValidationError(`Wesley generated leg "${legName}" field "files" must be an array`); | |
| } | |
| const files: string[] = []; | |
| for (const entry of value) { | |
| const file = requireJsonObject(entry, `Wesley generated leg "${legName}" file`); | |
| rejectUnknownKeys(file, ['path', 'size', 'contentHash', 'signature'], `Wesley generated leg "${legName}" file`); | |
| files.push(readRequiredString(file, 'path')); | |
| readRequiredNumber(file, 'size'); | |
| readRequiredString(file, 'contentHash'); | |
| readRequiredString(file, 'signature'); | |
| } | |
| return Object.freeze(files); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts` around lines
258 - 303, readGeneratedLegs currently accepts empty generatedLegs and doesn't
validate artifactCount against the actual files, allowing inconsistent sealed
manifests; update readGeneratedLegs (and readGeneratedFiles usage) so that after
calling readOptionalNumber(leg, 'artifactCount') and collecting files via
readGeneratedFiles(leg, name) you validate: if artifactCount is present and not
equal to files.length throw an AdapterValidationError with a clear message
referencing the leg name, and additionally reject an entirely empty
generatedLegs map by throwing an AdapterValidationError when names.length === 0;
use the existing AdapterValidationError type and the functions
readOptionalNumber, readGeneratedFiles, and rejectUnknownKeys to locate where to
insert these checks in readGeneratedLegs.
| { | ||
| "kind": "wesley.realization.manifest.v1", | ||
| "schemaPath": "schemas/continuum-receipt-family.graphql", | ||
| "canonicalSchemaPath": "/Users/james/git/continuum/schemas/continuum-receipt-family.graphql", |
There was a problem hiding this comment.
Replace machine-specific canonicalSchemaPath in shared fixture.
This absolute path is host-specific and makes the fixture non-portable across contributors/CI. Use a repo-relative value or remove the optional field in this fixture.
Suggested fix
- "canonicalSchemaPath": "/Users/james/git/continuum/schemas/continuum-receipt-family.graphql",
+ "canonicalSchemaPath": "schemas/continuum-receipt-family.graphql",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "canonicalSchemaPath": "/Users/james/git/continuum/schemas/continuum-receipt-family.graphql", | |
| "canonicalSchemaPath": "schemas/continuum-receipt-family.graphql", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/fixtures/continuum/receipt-family-wesley-realization-manifest.json` at
line 4, The fixture contains a machine-specific absolute "canonicalSchemaPath"
value which breaks portability; edit the
receipt-family-wesley-realization-manifest.json fixture to either remove the
optional "canonicalSchemaPath" field entirely or replace its value with a
repo-relative path (e.g., "./schemas/continuum-receipt-family.graphql") so the
fixture is consistent across contributors/CI and no host-specific absolute path
remains.
| function makeDescriptor(authority: string): ContinuumArtifactDescriptor { | ||
| return new ContinuumArtifactDescriptor({ | ||
| familyId: 'receipt-family', | ||
| sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', |
There was a problem hiding this comment.
Use a repo-neutral schema path fixture string.
The ~/git/continuum/... path is environment-shaped and makes fixture semantics less portable. Replace with a stable repo-relative token/path constant used across the suite.
🧩 Suggested cleanup
+const RECEIPT_SCHEMA_PATH = 'schemas/continuum/continuum-receipt-family.graphql';
+
function makeDescriptor(authority: string): ContinuumArtifactDescriptor {
return new ContinuumArtifactDescriptor({
familyId: 'receipt-family',
- sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql',
+ sourceSchemaPath: RECEIPT_SCHEMA_PATH,
@@
- sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql',
+ sourceSchemaPath: RECEIPT_SCHEMA_PATH,
@@
- sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql',
+ sourceSchemaPath: RECEIPT_SCHEMA_PATH,Also applies to: 77-77, 104-104, 113-113, 135-135, 145-145
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts` at line
14, The test uses an environment-specific fixture value for sourceSchemaPath
('~/git/continuum/...') which should be replaced with a repo-neutral constant;
update the test ContinuumArtifactIngestionPolicy.test.ts to import or reference
the shared test fixture constant (e.g., SCHEMA_FIXTURE_PATH or a suite-level
RELATIVE_SCHEMA_PATH) and replace all occurrences of sourceSchemaPath in the
file (including the other instances noted) with that constant so the path is
repo-relative and portable across environments.
| it('rejects local mirrors before they become authority', async () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| await expect(adapter.loadFile(generatedFixturePath, localMirrorContext)).rejects.toThrow(ContinuumArtifactAuthorityError); | ||
| }); | ||
|
|
||
| it('rejects self-attested authority fields inside artifact JSON', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(() => adapter.loadString(selfAttestedFixtureJson, fixtureContext)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('wraps invalid JSON syntax as adapter validation failure', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(() => adapter.loadString('{ "objectTypes": [', fixtureContext)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('rejects unsupported top-level JSON shapes', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(() => adapter.loadString('null', fixtureContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString('[]', fixtureContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString('{ "familyId": "receipt-family" }', fixtureContext)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('rejects unknown fixture keys and malformed nested entries', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(() => adapter.loadString(unknownFixtureFieldJson, fixtureContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString(invalidOperationJson, fixtureContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString(emptyOperationsJson, fixtureContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString(invalidFootprintsJson, fixtureContext)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('accepts fixture artifacts with omitted optional footprints', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(adapter.loadString(fixtureWithoutFootprintsJson, fixtureContext).artifactKind).toBe('continuum.family.fixture'); | ||
| }); | ||
|
|
||
| it('requires source schema context for fixture artifacts', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
| const contextWithoutSchemaPath: ContinuumArtifactJsonLoadContext = { | ||
| familyId: 'receipt-family', | ||
| authority: 'generated-fixture', | ||
| }; | ||
|
|
||
| expect(() => adapter.loadString(typeMapFixtureJson, contextWithoutSchemaPath)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('rejects unsealed Wesley realization manifests', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(() => adapter.loadString(unsealedWesleyManifestJson, artifactContext)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('rejects malformed Wesley realization manifest numbers and targets', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
|
|
||
| expect(() => adapter.loadString(invalidWesleyArtifactCountJson, artifactContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString(invalidWesleyGeneratedFilesJson, artifactContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString(invalidWesleyTargetsJson, artifactContext)).toThrow(AdapterValidationError); | ||
| expect(() => adapter.loadString(invalidWesleyTargetEntryJson, artifactContext)).toThrow(AdapterValidationError); | ||
| }); | ||
|
|
||
| it('accepts Wesley generated legs before the compiler writes file inventory', () => { | ||
| const adapter = new ContinuumArtifactJsonFileAdapter(); | ||
| const descriptor = adapter.loadString(wesleyManifestWithoutGeneratedFilesJson, artifactContext); | ||
|
|
||
| expect(descriptor.generatedLegs).toEqual(['warpTtd']); | ||
| expect(descriptor.generatedFiles).toEqual([]); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Add regression tests for shape-binding and generated inventory parity.
This suite still misses critical cases: (1) fixture loaded with generated-artifact authority and Wesley manifest loaded with generated-fixture authority should fail, (2) explicit artifactCount must match files.length, and (3) empty generatedLegs should fail for sealed manifests.
As per coding guidelines **/*.test.{ts,tsx}: Touched code in refactor slices must reach 100% test coverage before the slice is considered done.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts`
around lines 276 - 349, Add three regression tests to the
ContinuumArtifactJsonFileAdapter test suite: (1) create a fixture JSON loaded
via ContinuumArtifactJsonFileAdapter.loadString with authority
'generated-artifact' and attempt to load a Wesley manifest JSON with authority
'generated-fixture' using loadString, asserting it throws AdapterValidationError
(shape-binding/authority mismatch); (2) add a test that constructs a sealed
Wesley manifest where manifest.artifactCount !== manifest.files.length and
assert ContinuumArtifactJsonFileAdapter.loadString throws AdapterValidationError
(ensure explicit artifactCount must match files.length); and (3) add a test that
creates a sealed Wesley manifest with generatedLegs = [] and assert loadString
throws AdapterValidationError (empty generatedLegs rejected for sealed
manifests). Use existing fixture JSON constants and the
ContinuumArtifactJsonFileAdapter class and its loadString/loadFile calls to
locate where to add these tests.
Summary
authorityfields and local/handwritten mirror authority.Supersedes #90; this PR replaces the prefixed branch with
v18-continuum-opening.Validation
npm run lintnpm run typecheck:srcnpm run typecheck:testnpm run typecheck:surfacenpm run typecheck:policynpm run lint:sludgenpm run lint:quarantine-graduatenpx --yes markdownlint-cli docs/BEARING.md docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.mdnpx vitest run test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts test/unit/domain/index.exports.test.ts test/unit/domain/errors/index.test.tsnpm run test:local(447 files, 6821 tests)npm run test:coverage:ci(92.14% lines; threshold 91.90%)Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests