diff --git a/.github/workflows/archcontext-organization-runner.yml b/.github/workflows/archcontext-organization-runner.yml index 61674ffd..77de5114 100644 --- a/.github/workflows/archcontext-organization-runner.yml +++ b/.github/workflows/archcontext-organization-runner.yml @@ -27,7 +27,7 @@ on: description: ArchContext runtime version expected by this workflow release. required: false type: string - default: "0.4.0" + default: "0.4.1" fail_on: description: Whether a failing deterministic gate should fail the job. required: false diff --git a/actions/review-action/action.yml b/actions/review-action/action.yml index 8782e2f2..4f74b6a9 100644 --- a/actions/review-action/action.yml +++ b/actions/review-action/action.yml @@ -20,7 +20,7 @@ inputs: runtime-version: description: ArchContext runtime version expected by this action release. required: false - default: "0.4.0" + default: "0.4.1" runtime-artifact-url: description: HTTPS URL for the pinned ArchContext runtime artifact. required: true diff --git a/deploy/release-checklists/archctx-0.4.1.md b/deploy/release-checklists/archctx-0.4.1.md new file mode 100644 index 00000000..ebed3a9b --- /dev/null +++ b/deploy/release-checklists/archctx-0.4.1.md @@ -0,0 +1,12 @@ +# archctx 0.4.1 release checklist + +Scope: publish the additive `ProjectionRequestV1.acceptedChange` bridge required by repo-harness architecture refresh handling. This patch does not change the v1 protocol identifiers or the Node engine range. + +- [ ] `bun run verify` passes under Node `>=24 <26`. +- [ ] `archctx` and `archctx-contracts` package versions are exactly `0.4.1`. +- [ ] Focused projection contract and CLI tests prove unresolved and accepted major-change paths. +- [ ] Pull request is merged to `main`; Claude review is waived by the user's explicit instruction. +- [ ] Publish `archctx-contracts@0.4.1` and read back version, integrity, and shasum. +- [ ] Publish `archctx@0.4.1` and read back version, integrity, shasum, Node engine, and exact CodeGraph dependency. +- [ ] Clean-room Node 24 smoke returns the `0.4.1` capabilities handshake. +- [ ] Update repo-harness to exact `archctx@0.4.1` and `archctx-contracts@0.4.1`, then prove the accepted refresh signal end to end. diff --git a/docs/examples/github-hosted-runner-workflow.yml b/docs/examples/github-hosted-runner-workflow.yml index ffa318b0..9d9545d0 100644 --- a/docs/examples/github-hosted-runner-workflow.yml +++ b/docs/examples/github-hosted-runner-workflow.yml @@ -30,8 +30,8 @@ jobs: trust-level: organization fail-on: blocking fork-pr-mode: unsupported - runtime-version: "0.4.0" - runtime-artifact-url: https://archcontext.repoharness.com/releases/archctx-0.4.0.tgz + runtime-version: "0.4.1" + runtime-artifact-url: https://archcontext.repoharness.com/releases/archctx-0.4.1.tgz runtime-artifact-digest: sha256: expected-repository: expected-head-sha: ${{ github.event.pull_request.head.sha }} diff --git a/docs/examples/reusable-organization-runner-caller.yml b/docs/examples/reusable-organization-runner-caller.yml index 77bf1168..35210f65 100644 --- a/docs/examples/reusable-organization-runner-caller.yml +++ b/docs/examples/reusable-organization-runner-caller.yml @@ -21,8 +21,8 @@ jobs: expected_repository: ${{ github.repository }} expected_head_sha: ${{ github.event.pull_request.head.sha }} expected_head_tree_oid: - runtime_artifact_url: https://archcontext.repoharness.com/releases/archctx-0.4.0.tgz + runtime_artifact_url: https://archcontext.repoharness.com/releases/archctx-0.4.1.tgz runtime_artifact_digest: sha256: - runtime_version: "0.4.0" + runtime_version: "0.4.1" fail_on: blocking fork_pr_mode: unsupported diff --git a/docs/runbooks/personal-user-install.md b/docs/runbooks/personal-user-install.md index 22d059f5..4e0187c6 100644 --- a/docs/runbooks/personal-user-install.md +++ b/docs/runbooks/personal-user-install.md @@ -1,12 +1,12 @@ # Personal User Install -This runbook is the current individual-user install path for `archctx@0.4.0`. +This runbook is the current individual-user install path for `archctx@0.4.1`. Team collaboration, design-partner rollout, opt-in beta cohorts, and shared organization rollout evidence are deferred. ## Scope - Target user: one local developer using ArchContext on their own repository. -- Required release artifact: public npm package `archctx@0.4.0` (`latest`). +- Required release artifact: public npm package `archctx@0.4.1` (`latest`). - Runtime: Node.js 24.x or 25.x, npm, and git must be available on `PATH`. - Not required for this path: GitHub App installation, ArchContext Cloud token, Cloudflare deploy access, or LLM provider credentials. - Do not treat this runbook as approved for a published package version until `docs/verification/fg6-release-distribution-readback.json` is verified for that exact version. @@ -26,7 +26,7 @@ archctx --help For pinned reproduction of the current verified release: ```bash -npm install -g archctx@0.4.0 +npm install -g archctx@0.4.1 ``` The npm package is the generated public `archctx` artifact. The repository root package `archcontext` and workspace packages remain private source manifests for building and verifying that artifact. diff --git a/docs/runbooks/trusted-runner.md b/docs/runbooks/trusted-runner.md index 0c1a1c25..591fcded 100644 --- a/docs/runbooks/trusted-runner.md +++ b/docs/runbooks/trusted-runner.md @@ -48,8 +48,8 @@ jobs: trust-level: organization fail-on: blocking fork-pr-mode: unsupported - runtime-version: "0.4.0" - runtime-artifact-url: https://archcontext.repoharness.com/releases/archctx-0.4.0.tgz + runtime-version: "0.4.1" + runtime-artifact-url: https://archcontext.repoharness.com/releases/archctx-0.4.1.tgz runtime-artifact-digest: sha256: expected-repository: expected-head-sha: diff --git a/docs/verification/fg4-deterministic-conclusion-readback.json b/docs/verification/fg4-deterministic-conclusion-readback.json index b454c6dc..5b56dd63 100644 --- a/docs/verification/fg4-deterministic-conclusion-readback.json +++ b/docs/verification/fg4-deterministic-conclusion-readback.json @@ -3,7 +3,7 @@ "environment": "process-fixture", "status": "verified", "ok": true, - "generatedAt": "2026-08-09T01:53:27.571Z", + "generatedAt": "2026-08-09T19:01:30.486Z", "evidence": { "processLevelFixture": true, "providerEnvCleared": { @@ -14,7 +14,7 @@ }, "deterministicGate": { "llmProviderConfigured": false, - "modelDigest": "sha256:c1c68a62e938fed39ee93a3ef5597a8281f2e7a4a5d03dbcb56e75f944e9a77c", + "modelDigest": "sha256:ff2e3ae2764b615e6152d9bff09d7170da4dcc2485d398044d4716f233ae493f", "result": "pass", "reviewDigestMatchesAttestation": true }, diff --git a/docs/verification/fg6-no-provider-deterministic-readback.json b/docs/verification/fg6-no-provider-deterministic-readback.json index b57b21fd..67804055 100644 --- a/docs/verification/fg6-no-provider-deterministic-readback.json +++ b/docs/verification/fg6-no-provider-deterministic-readback.json @@ -4,7 +4,7 @@ "environment": "staging-release-readback", "status": "verified", "ok": true, - "generatedAt": "2026-08-09T01:53:27.613Z", + "generatedAt": "2026-08-09T19:01:30.532Z", "sources": { "localNoCloudSource": "docs/verification/fg6-local-no-cloud-readback.json", "runnerSource": "docs/verification/fg4-github-hosted-runner-readback.json", @@ -55,7 +55,7 @@ }, "deterministicGate": { "llmProviderConfigured": false, - "modelDigest": "sha256:c1c68a62e938fed39ee93a3ef5597a8281f2e7a4a5d03dbcb56e75f944e9a77c", + "modelDigest": "sha256:ff2e3ae2764b615e6152d9bff09d7170da4dcc2485d398044d4716f233ae493f", "result": "pass", "reviewDigestMatchesAttestation": true }, diff --git a/package.json b/package.json index 5176eed5..44649b50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "archcontext", - "version": "0.4.0", + "version": "0.4.1", "private": true, "type": "module", "packageManager": "bun@1.3.10", diff --git a/packages/cloud/package.json b/packages/cloud/package.json index 50a3a7bf..49be096c 100644 --- a/packages/cloud/package.json +++ b/packages/cloud/package.json @@ -1,6 +1,6 @@ { "name": "@archcontext/cloud", - "version": "0.4.0", + "version": "0.4.1", "private": true, "type": "module", "exports": { diff --git a/packages/contracts/fixtures/valid/archctx-capabilities.json b/packages/contracts/fixtures/valid/archctx-capabilities.json index 50c1df4e..3892eac5 100644 --- a/packages/contracts/fixtures/valid/archctx-capabilities.json +++ b/packages/contracts/fixtures/valid/archctx-capabilities.json @@ -1,6 +1,6 @@ { "schemaVersion": "archcontext.capabilities/v1", - "package": { "name": "archctx", "version": "0.4.0" }, + "package": { "name": "archctx", "version": "0.4.1" }, "protocols": { "projectionRequest": "archcontext.projection-request/v1", "projectionResult": "archcontext.projection-result/v1", diff --git a/packages/contracts/fixtures/valid/product-version-manifest.json b/packages/contracts/fixtures/valid/product-version-manifest.json index 46366559..6cca72a7 100644 --- a/packages/contracts/fixtures/valid/product-version-manifest.json +++ b/packages/contracts/fixtures/valid/product-version-manifest.json @@ -2,7 +2,7 @@ "schemaVersion": "archcontext.product-version-manifest/v1", "product": { "name": "archctx", - "version": "0.4.0", + "version": "0.4.1", "distribution": "one-package" }, "packageManager": "bun@1.3.10", @@ -12,25 +12,25 @@ "surfaces": { "cli": { "packageName": "@archcontext/surfaces", - "version": "0.4.0", + "version": "0.4.1", "entrypoint": "archctx" }, "daemon": { "packageName": "@archcontext/local-runtime", - "version": "0.4.0", + "version": "0.4.1", "entrypoint": "archctx daemon start", "rpcSchemaVersion": "archcontext.runtime-rpc/v1" }, "mcp": { "packageName": "@archcontext/surfaces", - "version": "0.4.0", + "version": "0.4.1", "entrypoint": "archctx mcp", "transport": "stdio" } }, "schemas": { "schemaSetVersion": "2026-06-25.al0-ledger", - "contractsPackageVersion": "0.4.0", + "contractsPackageVersion": "0.4.1", "reviewChallenge": "archcontext.review-challenge/v2", "attestation": "archcontext.attestation/v2", "deviceIdentity": "archcontext.device-identity/v1", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 39e6cbca..25b96bb4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@archcontext/contracts", - "version": "0.4.0", + "version": "0.4.1", "private": false, "type": "module", "license": "Apache-2.0", diff --git a/packages/contracts/src/product-version.ts b/packages/contracts/src/product-version.ts index 2815c5b7..cf514d47 100644 --- a/packages/contracts/src/product-version.ts +++ b/packages/contracts/src/product-version.ts @@ -1,5 +1,5 @@ export const ARCHCONTEXT_PRODUCT_NAME = "archctx"; -export const ARCHCONTEXT_PRODUCT_VERSION = "0.4.0"; +export const ARCHCONTEXT_PRODUCT_VERSION = "0.4.1"; export const ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10"; export const ARCHCONTEXT_NODE_RANGE = ">=24 <26"; export const LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1"; diff --git a/packages/contracts/src/projection.ts b/packages/contracts/src/projection.ts index fb06a631..2480dc34 100644 --- a/packages/contracts/src/projection.ts +++ b/packages/contracts/src/projection.ts @@ -79,6 +79,7 @@ export interface ProjectionRequestV1 { changedPaths: string[]; expected: ProjectionExpectedSnapshotV1; adoptionPlanId?: string; + acceptedChange?: AcceptedArchitectureChangeReferenceV1; } export interface ProjectionSnapshotV1 extends ProjectionExpectedSnapshotV1 { @@ -193,12 +194,31 @@ export function archctxCapabilities(packageVersion: string): ArchctxCapabilities export function projectionRequestInvariantIssues(input: ProjectionRequestV1): string[] { const issues = [ ...sortedUniqueIssues("targets", input.targets), - ...sortedUniqueIssues("changedPaths", input.changedPaths) + ...sortedUniqueIssues("changedPaths", input.changedPaths), + ...(input.acceptedChange + ? [ + ...sortedUniqueIssues("acceptedChange.reasonCodes", input.acceptedChange.reasonCodes), + ...sortedUniqueIssues("acceptedChange.affectedNodeIds", input.acceptedChange.affectedNodeIds) + ] + : []) ]; if (input.targets.length === 0) issues.push("targets must contain at least one projection target"); if (!/^[a-zA-Z0-9_.:-]+$/.test(input.requestId)) issues.push("requestId must use the stable identifier character set"); if (input.mode === "adopt" && !input.adoptionPlanId) issues.push("adoptionPlanId is required when mode=adopt"); if (input.mode !== "adopt" && input.adoptionPlanId !== undefined) issues.push("adoptionPlanId is only allowed when mode=adopt"); + if (input.acceptedChange) { + if (input.acceptedChange.changeSetId.trim() === "") issues.push("acceptedChange.changeSetId must not be empty"); + if (input.acceptedChange.eventId.trim() === "") issues.push("acceptedChange.eventId must not be empty"); + if (input.acceptedChange.reasonCodes.length === 0) issues.push("acceptedChange.reasonCodes must contain at least one reason"); + if (input.acceptedChange.affectedNodeIds.length === 0) issues.push("acceptedChange.affectedNodeIds must contain at least one node"); + const allowedReasons = new Set(ARCHITECTURE_MAJOR_CHANGE_REASON_CODES); + for (const reason of input.acceptedChange.reasonCodes) { + if (!allowedReasons.has(reason)) issues.push(`acceptedChange.reasonCodes contains unsupported reason: ${reason}`); + } + for (const nodeId of input.acceptedChange.affectedNodeIds) { + if (nodeId.trim() === "") issues.push("acceptedChange.affectedNodeIds must not contain empty node ids"); + } + } return issues; } diff --git a/packages/contracts/test/contracts.test.ts b/packages/contracts/test/contracts.test.ts index 6b492670..849cae78 100644 --- a/packages/contracts/test/contracts.test.ts +++ b/packages/contracts/test/contracts.test.ts @@ -168,13 +168,31 @@ const schemaByFixture: Record = { test("projection request contract enforces adopt binding, unique arrays, and canonical ordering", () => { const schema = readJson("schemas/runtime/projection-request.schema.json"); const valid = readJson("packages/contracts/fixtures/valid/projection-request.json") as unknown as ProjectionRequestV1; + const acceptedChange = { + changeSetId: "changeset.runtime-major", + eventId: "architecture_event.runtime-major", + reasonCodes: ["ownership-changed", "responsibility-changed"] as const, + affectedNodeIds: ["capability.runtime-harness.hook-adapters"] + }; expect(projectionRequestInvariantIssues(valid)).toEqual([]); + expect(projectionRequestInvariantIssues({ ...valid, acceptedChange: { ...acceptedChange, reasonCodes: [...acceptedChange.reasonCodes] } })).toEqual([]); + expect(validateJsonSchema(schema as any, { ...valid, acceptedChange } as any).valid).toBe(true); expect(validateJsonSchema(schema as any, { ...valid, mode: "adopt" } as any).valid).toBe(false); expect(validateJsonSchema(schema as any, { ...valid, adoptionPlanId: "adoption_plan.example" } as any).valid).toBe(false); expect(validateJsonSchema(schema as any, { ...valid, changedPaths: [valid.changedPaths[0], valid.changedPaths[0]] } as any).valid).toBe(false); + expect(validateJsonSchema(schema as any, { ...valid, acceptedChange: { ...acceptedChange, eventId: "" } } as any).valid).toBe(false); + expect(validateJsonSchema(schema as any, { ...valid, acceptedChange: { ...acceptedChange, reasonCodes: ["not-supported"] } } as any).valid).toBe(false); + expect(validateJsonSchema(schema as any, { ...valid, acceptedChange: { ...acceptedChange, untyped: true } } as any).valid).toBe(false); expect(projectionRequestInvariantIssues({ ...valid, changedPaths: [...valid.changedPaths].reverse() })).toEqual([ "changedPaths must be sorted and unique" ]); + expect(projectionRequestInvariantIssues({ + ...valid, + acceptedChange: { + ...acceptedChange, + reasonCodes: [...acceptedChange.reasonCodes].reverse() + } + })).toEqual(["acceptedChange.reasonCodes must be sorted and unique"]); }); test("JSON schema uniqueItems compares canonical JSON rather than object insertion order", () => { @@ -281,7 +299,7 @@ test("refresh-required signals bind the exact accepted ChangeSet event and taxon test("capabilities fixture is the exact static handshake advertised by contracts", () => { const fixture = readJson("packages/contracts/fixtures/valid/archctx-capabilities.json") as unknown as ReturnType; - expect(archctxCapabilities("0.4.0")).toEqual(fixture); + expect(archctxCapabilities("0.4.1")).toEqual(fixture); expect([...ARCHCTX_FEATURES]).toEqual([...ARCHCTX_FEATURES].sort()); const schema = readJson("schemas/runtime/archctx-capabilities.schema.json"); expect(validateJsonSchema(schema as any, archctxCapabilities("1.2.3-rc.1+build.5") as any).valid).toBe(true); diff --git a/packages/core/package.json b/packages/core/package.json index 5e55005d..4414bd2a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@archcontext/core", - "version": "0.4.0", + "version": "0.4.1", "private": true, "type": "module", "exports": { diff --git a/packages/core/practice-catalog/assets/catalog.yaml b/packages/core/practice-catalog/assets/catalog.yaml index bb01109b..2c9de675 100644 --- a/packages/core/practice-catalog/assets/catalog.yaml +++ b/packages/core/practice-catalog/assets/catalog.yaml @@ -1,7 +1,7 @@ { "schemaVersion": "archcontext.practice-catalog-manifest/v1", "catalogVersion": "2026.06.0", - "productVersion": "0.4.0", + "productVersion": "0.4.1", "generatedAt": "1970-01-01T00:00:00.000Z", "entries": [ { @@ -389,5 +389,5 @@ "structurizr.dsl", "twelve-factor" ], - "catalogDigest": "sha256:61835a0efb9c5a19cb4fd16482026918785d01b94b6e60f2c2a06b64a4c713be" + "catalogDigest": "sha256:4d9f972043884502bdfc43a81b7f351df59a5f2af0231529fcdb49490bbf25d7" } diff --git a/packages/local-runtime/package.json b/packages/local-runtime/package.json index bc55d1a9..2f0f7c88 100644 --- a/packages/local-runtime/package.json +++ b/packages/local-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@archcontext/local-runtime", - "version": "0.4.0", + "version": "0.4.1", "private": true, "type": "module", "exports": { diff --git a/packages/surfaces/cli/src/main.ts b/packages/surfaces/cli/src/main.ts index f74de9f2..4d2b627c 100755 --- a/packages/surfaces/cli/src/main.ts +++ b/packages/surfaces/cli/src/main.ts @@ -962,9 +962,11 @@ async function runArchitectureDocsProjectionCommand(args: string[], cwd: string, const generatedAt = readFlag(args, "--generated-at") ?? new Date(0).toISOString(); let profile: ArchitectureProjectionProfile; let projection: ReturnType; + let acceptedChange: AcceptedArchitectureChangeReferenceV1 | undefined; try { profile = architectureProjectionProfile(args); - projection = buildArchitectureDocsProjection(root, generatedAt, profile, undefined, acceptedArchitectureChange(args)); + acceptedChange = acceptedArchitectureChange(args); + projection = buildArchitectureDocsProjection(root, generatedAt, profile, undefined, acceptedChange); } catch (error) { return errorEnvelope(`docs.${subcommand}`, "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error)); } @@ -997,7 +999,7 @@ async function runArchitectureDocsProjectionCommand(args: string[], cwd: string, } as unknown as Json); } if (subcommand === "adopt") { - return runArchitectureDocsAdoptionCommand(args, root, daemon, projection, profile, generatedAt); + return runArchitectureDocsAdoptionCommand(args, root, daemon, projection, profile, generatedAt, acceptedChange); } if (projection.plan.rejected.length > 0) { return errorEnvelope("docs.plan", "AC_PRECONDITION_FAILED", `Architecture documentation projection requires explicit adoption or ownership repair: ${projection.plan.rejected.map((diff) => `${diff.path} (${diff.reasonCode})`).join(", ")}`); @@ -1172,7 +1174,8 @@ async function runArchitectureDocsAdoptionCommand( daemon: RuntimeDaemonClient, projection: ReturnType, profile: ArchitectureProjectionProfile, - generatedAt: string + generatedAt: string, + acceptedChange?: AcceptedArchitectureChangeReferenceV1 ) { if (profile !== REPO_HARNESS_PROJECTION_PROFILE) { return errorEnvelope("docs.adopt", "AC_SCHEMA_INVALID", `docs adopt requires --profile ${REPO_HARNESS_PROJECTION_PROFILE}`); @@ -1213,10 +1216,10 @@ async function runArchitectureDocsAdoptionCommand( } const simulatedByPath = new Map(projection.loaded.existingFiles.map((file) => [file.path, file])); for (const file of [...projection.plan.files, ...adoption.files]) simulatedByPath.set(file.path, file); - const canonicalFirst = buildArchitectureDocsProjection(root, generatedAt, profile, [...simulatedByPath.values()]); + const canonicalFirst = buildArchitectureDocsProjection(root, generatedAt, profile, [...simulatedByPath.values()], acceptedChange); const canonicalExistingByPath = new Map(simulatedByPath); for (const file of canonicalFirst.files) canonicalExistingByPath.set(file.path, file); - const canonical = buildArchitectureDocsProjection(root, generatedAt, profile, [...canonicalExistingByPath.values()]); + const canonical = buildArchitectureDocsProjection(root, generatedAt, profile, [...canonicalExistingByPath.values()], acceptedChange); if (!canonical.plan.drift.ok || canonical.plan.rejected.length > 0 || canonical.plan.projectionDigest !== canonicalFirst.plan.projectionDigest) { return errorEnvelope("docs.adopt", "AC_PRECONDITION_FAILED", "projection-adoption-fixed-point-unproven"); } @@ -1264,7 +1267,7 @@ async function runProjectionProtocolCommand(args: string[], cwd: string, daemon: const generatedAt = new Date(0).toISOString(); let projection: ReturnType; try { - projection = buildArchitectureDocsProjection(root, generatedAt, REPO_HARNESS_PROJECTION_PROFILE); + projection = buildArchitectureDocsProjection(root, generatedAt, REPO_HARNESS_PROJECTION_PROFILE, undefined, request.acceptedChange); assertProjectionExpectedSnapshot(request, root, projection); } catch (error) { return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", error instanceof Error ? error.message : String(error)); @@ -1282,11 +1285,11 @@ async function runProjectionProtocolCommand(args: string[], cwd: string, daemon: "--adoption-plan-id", request.adoptionPlanId!, "--expected-worktree-digest", expectedWorktreeDigest, "--task-session-id", request.requestId - ], root, daemon, projection, REPO_HARNESS_PROJECTION_PROFILE, generatedAt); + ], root, daemon, projection, REPO_HARNESS_PROJECTION_PROFILE, generatedAt, request.acceptedChange); if (!adopted.ok) return adopted; let output: ReturnType; try { - output = buildArchitectureDocsProjection(root, generatedAt, REPO_HARNESS_PROJECTION_PROFILE); + output = buildArchitectureDocsProjection(root, generatedAt, REPO_HARNESS_PROJECTION_PROFILE, undefined, request.acceptedChange); if (!output.plan.drift.ok || output.plan.rejected.length > 0) { return errorEnvelope("projection.run", "AC_PRECONDITION_FAILED", "projection adoption did not reach the architecture-docs fixed point"); } @@ -1338,12 +1341,32 @@ function parseProjectionProtocolRequest(raw: string): ProjectionRequestV1 { if (typeof snapshot.repositoryId !== "string" || typeof snapshot.workspaceId !== "string") throw new Error("projection request repository/workspace identity is required"); if (typeof snapshot.headSha !== "string" || !/^[a-f0-9]{40}$/.test(snapshot.headSha)) throw new Error("projection request expected.headSha is invalid"); if (typeof snapshot.worktreeDigest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(snapshot.worktreeDigest)) throw new Error("projection request expected.worktreeDigest is invalid"); + if (input.acceptedChange !== undefined) validateProjectionAcceptedChange(input.acceptedChange); const request = input as unknown as ProjectionRequestV1; const issues = projectionRequestInvariantIssues(request); if (issues.length > 0) throw new Error(`projection request invariant failed: ${issues.join("; ")}`); return request; } +function validateProjectionAcceptedChange(value: unknown): void { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error("projection request acceptedChange must be an object"); + } + const accepted = value as Record; + const supportedKeys = new Set(["changeSetId", "eventId", "reasonCodes", "affectedNodeIds"]); + const unsupportedKey = Object.keys(accepted).find((key) => !supportedKeys.has(key)); + if (unsupportedKey) throw new Error(`projection request acceptedChange contains unsupported property: ${unsupportedKey}`); + if (typeof accepted.changeSetId !== "string" || typeof accepted.eventId !== "string") { + throw new Error("projection request acceptedChange changeSetId and eventId are required"); + } + if (!Array.isArray(accepted.reasonCodes) || accepted.reasonCodes.some((reason) => typeof reason !== "string")) { + throw new Error("projection request acceptedChange reasonCodes must be a string array"); + } + if (!Array.isArray(accepted.affectedNodeIds) || accepted.affectedNodeIds.some((nodeId) => typeof nodeId !== "string")) { + throw new Error("projection request acceptedChange affectedNodeIds must be a string array"); + } +} + function assertProjectionExpectedSnapshot( request: ProjectionRequestV1, root: string, diff --git a/packages/surfaces/cli/test/cli.test.ts b/packages/surfaces/cli/test/cli.test.ts index bff22b39..9c7b9366 100644 --- a/packages/surfaces/cli/test/cli.test.ts +++ b/packages/surfaces/cli/test/cli.test.ts @@ -12,7 +12,7 @@ import { SqliteLocalStore, migrateLegacyLocalStoreIfNeeded, runtimeStatePaths } import { initializeArchContextModel } from "@archcontext/local-runtime/model-store-yaml"; import { DevicePrivateKeyStore, InMemoryCredentialSecretStore, KeychainTokenStore } from "@archcontext/cloud/control-plane-client"; import { createReviewChallengeV2 } from "@archcontext/cloud/attestation"; -import { ARCHCONTEXT_PRODUCT_VERSION, ARCHCTX_FEATURES, archctxCapabilities, digestJson, projectionResultInvariantIssues, stableYaml, type ProjectionResultV1 } from "@archcontext/contracts"; +import { ARCHCONTEXT_PRODUCT_VERSION, ARCHCTX_FEATURES, archctxCapabilities, digestJson, projectionResultInvariantIssues, stableYaml, type ProjectionRequestV1, type ProjectionResultV1 } from "@archcontext/contracts"; import { runFastHookEnqueue } from "../src/hook-fast"; import { resolveCommandExitCode, runCapabilitiesCommand, runCli } from "../src/main"; @@ -144,6 +144,25 @@ test("CLI projection run consumes ProjectionRequestV1 and returns a receipt-vali expect(stale.ok).toBe(false); expect((stale as any).error?.code).toBe("AC_PRECONDITION_FAILED"); expect((stale as any).error?.message).toContain("worktreeDigest"); + + const partialAcceptance = await runTestCli("projection", ["run", "--request-json", JSON.stringify({ + ...request, + acceptedChange: { changeSetId: "changeset.partial" } + })], root); + expect(partialAcceptance.ok).toBe(false); + expect((partialAcceptance as any).error?.code).toBe("AC_SCHEMA_INVALID"); + + const nonCanonicalAcceptance = await runTestCli("projection", ["run", "--request-json", JSON.stringify({ + ...request, + acceptedChange: { + changeSetId: "changeset.noncanonical", + eventId: "architecture_event.noncanonical", + reasonCodes: ["responsibility-changed", "ownership-changed"], + affectedNodeIds: ["capability.z", "capability.a"] + } + })], root); + expect(nonCanonicalAcceptance.ok).toBe(false); + expect((nonCanonicalAcceptance as any).error?.message).toContain("sorted and unique"); } finally { removeTempRoot(root); } @@ -3359,6 +3378,47 @@ describe("archctx CLI", () => { const nodePath = join(root, ".archcontext/model/nodes/capability.runtime-harness.hook-adapters.yaml"); writeFileSync(nodePath, readFileSync(nodePath, "utf8").replace("Routes runtime hook events.", "Routes and validates runtime hook events."), "utf8"); + const unresolvedPlan = await runTestCli("docs", ["plan", "--profile", "repo-harness/v1"], root); + expect(unresolvedPlan.ok).toBe(true); + expect((unresolvedPlan.data as any).majorChange.mode).toBe("human-action-required"); + const unresolvedProvenance = (unresolvedPlan.data as any).provenance; + const protocolRequest: ProjectionRequestV1 = { + schemaVersion: "archcontext.projection-request/v1", + requestId: "projection_request.hook_adapters_major", + profile: "repo-harness/v1", + mode: "plan", + targets: ["agent-context", "architecture-docs"], + changedPaths: [".archcontext/model/nodes/capability.runtime-harness.hook-adapters.yaml"], + expected: { + repositoryId: repositoryFingerprint(root), + workspaceId: `workspace.${digestJson({ root: canonicalRepositoryRoot(root) } as any).replace(/^sha256:/, "").slice(0, 16)}`, + headSha: gitOut(root, "rev-parse", "HEAD"), + worktreeDigest: unresolvedProvenance.worktreeDigest + } + }; + const unresolvedProtocol = await runTestCli("projection", ["run", "--request-json", JSON.stringify(protocolRequest)], root); + expect(unresolvedProtocol.ok).toBe(true); + expect((unresolvedProtocol.data as ProjectionResultV1).status).toBe("human-action-required"); + expect((unresolvedProtocol.data as ProjectionResultV1).refreshSignals[0]?.mode).toBe("human-action-required"); + + const acceptedChange = { + changeSetId: "changeset.hook-adapters-major", + eventId: "architecture_event.hook-adapters-major", + reasonCodes: ["responsibility-changed"] as const, + affectedNodeIds: ["capability.runtime-harness.hook-adapters"] + }; + const acceptedProtocol = await runTestCli("projection", ["run", "--request-json", JSON.stringify({ + ...protocolRequest, + requestId: "projection_request.hook_adapters_major_accepted", + acceptedChange + })], root); + expect(acceptedProtocol.ok).toBe(true); + expect((acceptedProtocol.data as ProjectionResultV1).status).toBe("planned"); + expect((acceptedProtocol.data as ProjectionResultV1).refreshSignals[0]).toMatchObject({ + mode: "refresh-required", + acceptedChange + }); + const signalArgs = [ "plan", "--profile", "repo-harness/v1", "--accepted-change-set-id", "changeset.hook-adapters-major", diff --git a/packages/surfaces/package.json b/packages/surfaces/package.json index eccd993c..3b4627a5 100644 --- a/packages/surfaces/package.json +++ b/packages/surfaces/package.json @@ -1,6 +1,6 @@ { "name": "@archcontext/surfaces", - "version": "0.4.0", + "version": "0.4.1", "private": true, "type": "module", "bin": { diff --git a/plans/plan-20260810-0228-projection-accepted-change-protocol.md b/plans/plan-20260810-0228-projection-accepted-change-protocol.md new file mode 100644 index 00000000..eb280710 --- /dev/null +++ b/plans/plan-20260810-0228-projection-accepted-change-protocol.md @@ -0,0 +1,129 @@ +# Plan: Projection accepted-change protocol + +> **Status**: Executing +> **Created**: 20260810-0228 +> **Slug**: projection-accepted-change-protocol +> **Planning Source**: codex-plan-or-waza-think +> **Orchestration Kind**: host-plan +> **Source Ref**: (none) +> **Artifact Level**: work-package +> **Promotion Reason**: human_decision_boundary +> **Verification Boundary**: Commands named in the captured planning output plus `repo-harness run verify-contract --contract tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md --strict`. +> **Rollback Surface**: Before execution remove `plans/plan-20260810-0228-projection-accepted-change-protocol.md`; after execution revert branch `codex/projection-accepted-change-protocol` or the explicitly reviewed diff. +> **Spec**: `docs/spec.md` +> **Research**: See `docs/researches/` +> **Task Contract**: `tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md` +> **Task Review**: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` +> **Implementation Notes**: `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md` + +## Agentic Routing +- Selected route: planning +- Routing reason: Captured from codex-plan-or-waza-think planning output. +- Source ref: (none) +- Due diligence: + - P1 map: See captured planning output below. + - P2 trace: See captured planning output below. + - P3 decision rationale: See captured planning output below. + +## Workflow Inventory +Complete this inventory before implementation. If any line is unknown, keep the plan in Draft and fill it before projection. + +- Active plan: `plans/plan-20260810-0228-projection-accepted-change-protocol.md` +- Sprint contract: `tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md` +- Sprint review: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` +- Implementation notes: `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md` +- Deferred-goal ledger: `tasks/todos.md` +- Current checks: `.ai/harness/checks/latest.json` +- Run snapshots: `.ai/harness/runs/` +- Scope authority: `tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md` `allowed_paths` +- Concurrency rule: `.ai/harness/active-plan` selects the active plan for this worktree when present; `.ai/harness/active-worktree` records the owning worktree. If another worktree already owns active work, open or switch to the matching worktree instead of serializing unrelated plans. +- Execution isolation: approved contract-level work projects through `repo-harness run plan-to-todo --plan plans/plan-20260810-0228-projection-accepted-change-protocol.md` and may start `repo-harness run contract-worktree start --plan plans/plan-20260810-0228-projection-accepted-change-protocol.md`. + +## Approach +### Strategy +Use the captured planning output below as the execution source of truth. + +### Trade-offs +| Option | Pros | Cons | Decision | +|--------|------|------|----------| +| Captured plan | Preserves the approved Codex Plan or Waza think decision | Requires the captured text to be concrete enough to execute | Use | + +## Detailed Design +### File Changes +| File | Action | Description | +|------|--------|-------------| +| See captured planning output | Follow | Implement only the approved scope named below | + +### Code Snippets +See captured planning output. + +### Data Flow +See captured planning output. + +## Risk Assessment +| Risk | Likelihood | Impact | Mitigation | +|------|------------|--------|------------| +| Captured plan lacks enough detail | Medium | Execution may need clarification | Stop before implementation if the captured output contradicts repo rules or lacks concrete file targets | + +## Task Contracts +- Contract file: `tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md` +- Review file: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` +- Implementation notes file: `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md` +- Template: `.claude/templates/contract.template.md` +- Verification command: `repo-harness run verify-contract --contract tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md --strict` +- Active plan rule: this captured plan is written to `.ai/harness/active-plan` and the owning worktree is written to `.ai/harness/active-worktree` unless --no-active is used. Do not infer active execution from the latest non-archived plan. + +## Handoff + +- Checks file: `.ai/harness/checks/latest.json` +- Session handoff: `.ai/harness/handoff/current.md` + +## Promotion Gate + +- **Merge/PR unit**: Captured plan `plans/plan-20260810-0228-projection-accepted-change-protocol.md` is the proposed mergeable execution unit; revise before execute if this is only a checklist step. +- **Rollback surface**: Before execution remove `plans/plan-20260810-0228-projection-accepted-change-protocol.md`; after execution revert branch `codex/projection-accepted-change-protocol` or the explicitly reviewed diff. +- **Verification boundary**: Commands named in the captured planning output plus `repo-harness run verify-contract --contract tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md --strict`. +- **Review/acceptance boundary**: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` must record pass against the captured acceptance criteria. +- **High-risk surface**: Risks named in captured planning output; keep the plan Draft if risk ownership is not concrete. +- **Why not checklist row**: human_decision_boundary + +## Evidence Contract + +- **State/progress path**: `plans/plan-20260810-0228-projection-accepted-change-protocol.md` task breakdown, `tasks/todos.md` deferred-goal ledger, `tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md`, `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md`, and `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md` +- **Verification evidence**: `.ai/harness/checks/latest.json`, `.ai/harness/runs/`, and the commands named in the captured planning output +- **Evaluator rubric**: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` must record a passing Waza /check style recommendation +- **Stop condition**: all task breakdown items are complete, sprint verification passes, and the review recommends pass +- **Rollback surface**: Before execution remove `plans/plan-20260810-0228-projection-accepted-change-protocol.md`; after execution revert branch `codex/projection-accepted-change-protocol` or the explicitly reviewed diff. + +## Captured Planning Output + +# Projection accepted-change protocol + +## Goal +Thread an explicit typed accepted semantic change through ProjectionRequestV1 so repo-harness can resolve an ArchContext major-change signal without bypassing the projection protocol. + +## Scope +- Extend the additive v1 request schema and runtime validator with acceptedChange. +- Forward acceptedChange into deterministic projection classification. +- Add contract and CLI regression coverage. +- Publish archctx and archctx-contracts patch versions, then update repo-harness exact dependencies. + +## Task Breakdown +- [x] Extend ProjectionRequestV1, schema, and invariants. +- [x] Forward and validate acceptedChange in the projection CLI protocol. +- [x] Add regression tests for unresolved and accepted major-change paths. +- [x] Run release gates and prepare exact patch artifacts for post-merge publication. + +## Out of scope +- No heuristic classification of raw source edits. +- No HTML architecture output. +- No compatibility fallback or legacy request shape. + +## Annotations + + +## Task Breakdown +- [x] Extend ProjectionRequestV1, schema, and invariants. +- [x] Forward and validate acceptedChange in the projection CLI protocol. +- [x] Add regression tests for unresolved and accepted major-change paths. +- [x] Run release gates and prepare exact patch artifacts for post-merge publication. diff --git a/schemas/runtime/projection-request.schema.json b/schemas/runtime/projection-request.schema.json index 1b60c27b..de61a7f1 100644 --- a/schemas/runtime/projection-request.schema.json +++ b/schemas/runtime/projection-request.schema.json @@ -24,7 +24,8 @@ "items": { "$ref": "#/$defs/repoPath" } }, "expected": { "$ref": "#/$defs/expectedSnapshot" }, - "adoptionPlanId": { "type": "string", "pattern": "^adoption_plan\\.[a-zA-Z0-9_.-]+$" } + "adoptionPlanId": { "type": "string", "pattern": "^adoption_plan\\.[a-zA-Z0-9_.-]+$" }, + "acceptedChange": { "$ref": "#/$defs/acceptedChange" } }, "allOf": [ { @@ -46,6 +47,45 @@ ] } }, + "acceptedChange": { + "type": "object", + "additionalProperties": false, + "required": ["changeSetId", "eventId", "reasonCodes", "affectedNodeIds"], + "properties": { + "changeSetId": { "type": "string", "minLength": 1 }, + "eventId": { "type": "string", "minLength": 1 }, + "reasonCodes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "description": "Canonical wire order is lexicographic and is enforced by projectionRequestInvariantIssues.", + "items": { + "enum": [ + "constraint-changed", + "entrypoint-changed", + "interface-changed", + "lifecycle-changed", + "node-added", + "node-moved", + "node-removed", + "node-renamed", + "ownership-changed", + "relation-changed", + "responsibility-changed", + "risk-boundary-changed", + "verified-flow-proof-changed" + ] + } + }, + "affectedNodeIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "description": "Canonical wire order is lexicographic and is enforced by projectionRequestInvariantIssues.", + "items": { "type": "string", "minLength": 1 } + } + } + }, "expectedSnapshot": { "type": "object", "additionalProperties": false, diff --git a/tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md b/tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md new file mode 100644 index 00000000..22a22788 --- /dev/null +++ b/tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md @@ -0,0 +1,163 @@ +# Task Contract: projection-accepted-change-protocol + +> **Status**: Active +> **Plan**: plans/plan-20260810-0228-projection-accepted-change-protocol.md +> **Task Profile**: code-change +> +> **Owner**: ancienttwo +> **Capability ID**: architecture-projection +> **Last Updated**: 2026-08-10 02:35 +> **Review File**: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` +> **Notes File**: `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md` +> **Exemplar**: `docs/reference-configs/contract-brief-example.md` + +## Why + +The stable repo-harness projection provider cannot currently carry the explicit acceptance that turns an ArchContext major semantic delta from `human-action-required` into `refresh-required`. Without this bridge, a legitimate architecture refresh cannot complete through the cross-repository protocol. + +## Goal + +Thread an explicit typed accepted semantic change through `ProjectionRequestV1`, publish `archctx` and `archctx-contracts` 0.4.1, and let repo-harness resolve a major-change signal without bypassing the provider protocol. + +## Scope + +- In scope: additive request contract, JSON schema, runtime validation, deterministic projection forwarding, regression coverage, aligned patch-release surfaces, and a release checklist. +- Out of scope: heuristic source-edit classification, HTML output, legacy request fallback, or a second protocol version. +- Taste constraints: architecture output remains Markdown with Mermaid; no generated HTML. + +## Stop Conditions + +- Stop before writing outside Allowed Paths; widen this contract explicitly first if a verified release surface is missing. +- Stop if the accepted reference can bypass schema or invariant validation. +- Stop if the change requires a second projection protocol version rather than an additive optional field. + +## Falsifier + +The direction is wrong if an accepted request still returns `human-action-required`, or if an absent/partial/unsorted/unsupported accepted reference reaches `refresh-required`. The cheapest proof point is the focused contracts and CLI projection tests. + +## Root Cause Evidence + +Not applicable: this contract is a shared-protocol code change, not the `bugfix` task profile. + +## Workflow Inventory + +- Source plan: `plans/plan-20260810-0228-projection-accepted-change-protocol.md` +- Deferred-goal ledger: `tasks/todos.md` +- Review file: `tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md` +- Notes file: `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md` +- Checks file: `.ai/harness/checks/latest.json` +- Run snapshots: `.ai/harness/runs/` +- Scope gate: edit only paths listed under `allowed_paths`; update this contract before widening scope. +- Completion gate: run `verify-sprint --prepare-acceptance`, record one typed AcceptanceReceipt under the frozen policy below, then run `verify-sprint`; review Markdown is projection only. + +## Acceptance Policy + +```json +{"protocol":1,"reviewer":"Claude","user_waiver":"allowed"} +``` + +## Allowed Paths + +```yaml +allowed_paths: + - plans/plan-20260810-0228-projection-accepted-change-protocol.md + - tasks/todos.md + - tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md + - tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md + - tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md + - package.json + - bun.lock + - .github/workflows/archcontext-organization-runner.yml + - actions/review-action/action.yml + - docs/examples/github-hosted-runner-workflow.yml + - docs/examples/reusable-organization-runner-caller.yml + - docs/runbooks/personal-user-install.md + - docs/runbooks/trusted-runner.md + - packages/cloud/package.json + - packages/contracts/package.json + - packages/contracts/src/product-version.ts + - packages/contracts/src/projection.ts + - packages/contracts/fixtures/ + - packages/contracts/test/ + - packages/core/package.json + - packages/core/practice-catalog/assets/catalog.yaml + - packages/local-runtime/package.json + - packages/surfaces/package.json + - packages/surfaces/cli/src/main.ts + - packages/surfaces/cli/test/cli.test.ts + - schemas/runtime/projection-request.schema.json + - deploy/release-checklists/ +``` + +## Evidence Requirements + +```yaml +evidence_requirements: + # Set benchmark to required when this contract consumes the harness profile benchmark matrix. + benchmark: not_applicable +``` + +## Delegation Contract + +```yaml +delegation: + budget: + tokens: null + runner_invocations: null + wall_time_minutes: null + permission_scope: + mode: inherit_allowed_paths + writable_paths: [] + network: inherited + roles: + parent: + mode: narrate_and_gatekeep + purpose: approval_checkpoint_owner + explorer: + mode: read_only + purpose: codebase_research + worker: + mode: edit_within_allowed_paths + purpose: implementation + verifier: + mode: read_only + purpose: exit_criteria_review + runner: + preferred: + - subagent + - codex-exec + - main-thread + fallback: main-thread + brief_is_authoritative: true +``` + +## Exit Criteria (Machine Verifiable) + +```yaml +exit_criteria: + files_exist: + - packages/contracts/src/product-version.ts + - packages/contracts/src/projection.ts + - schemas/runtime/projection-request.schema.json + artifacts_exist: + - .ai/harness/checks/latest.json + - tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md + tests_pass: + - path: packages/contracts/test/contracts.test.ts + - path: packages/surfaces/cli/test/cli.test.ts + commands_succeed: + - bun run typecheck + - bun test packages/contracts/test/contracts.test.ts packages/surfaces/cli/test/cli.test.ts + - bun run verify +``` + +## Acceptance Notes (Human Review) + +- Functional behavior: accepted major-change references produce `refresh-required`; absent references remain `human-action-required`. +- Edge cases: malformed, empty, unsorted, unsupported, partial, or extra-property accepted references fail closed. +- Regression risks: request receipt identity changes only when the optional field is present; post-adoption fixed-point builds preserve the same accepted reference. + +## Rollback Point + +- Commit / checkpoint: `9e3c040eb285d21c040edfb2da8f39eb22fbe98f` +- Revert strategy: revert the protocol patch and publish a forward patch release if already public. diff --git a/tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md b/tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md new file mode 100644 index 00000000..f394d10e --- /dev/null +++ b/tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md @@ -0,0 +1,53 @@ +# Implementation Notes: projection-accepted-change-protocol + +> **Status**: Active +> **Plan**: plans/plan-20260810-0228-projection-accepted-change-protocol.md +> **Contract**: tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md +> **Review**: tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md +> **Last Updated**: 2026-08-10 02:35 +> **Lifecycle**: notes + +## Design Decisions + +- Keep protocol identity at `archcontext.projection-request/v1`; `acceptedChange` is an additive optional field with the existing `AcceptedArchitectureChangeReferenceV1` shape. +- Validate acceptance at all three boundaries: JSON schema, contract invariants, and CLI raw-input parsing. Invalid or partial input fails closed. +- Forward the same accepted reference through initial projection and adoption fixed-point rebuilds so classification and receipt identity cannot diverge. +- Align the patch release at `0.4.1` across the root product, five internal workspaces, contracts fixtures, practice catalog, action/workflow pins, and install runbooks. +- Preserve the Node contract `>=24 <26`; verification ran with Node `24.19.0` because the host default Node `26.5.0` is intentionally outside the supported range. + +## Deviations From Plan Or Spec + +- Publication is a post-merge delivery action. This work package prepares and verifies the exact 0.4.1 source candidate; npm publication happens only from merged `main`. +- `plan-to-todo` replaced the originally captured contract with a generic scaffold. The contract was restored to the approved goal, explicit paths, and real exit criteria before further scope expansion. + +## Tradeoffs Considered + +| Option | Decision | Reason | +|--------|----------|--------| +| Add ProjectionRequestV2 | Reject | The accepted reference is additive and does not change existing request semantics. | +| Infer acceptance from changed paths | Reject | Acceptance is human authority and must remain explicit, typed, and receipt-bound. | +| Run archctx under repo-harness Node | Reject | The two products may have different Node engines; the caller must select a runtime satisfying the provider's range. | + +## Open Questions + +- None. + +## Evidence Links + +- Checks: `.ai/harness/checks/latest.json` +- Run snapshots: `.ai/harness/runs/` +- `bun run typecheck`: pass. +- Focused contracts suite: 176 pass, 0 fail. +- Focused stable-protocol and major-change projection tests: pass, including unresolved `human-action-required`, accepted `refresh-required`, partial input rejection, and non-canonical input rejection. +- `bun run verify` with Node 24.19.0: pass; 1218 tests pass, 0 fail, Mermaid verification covers 3 diagrams, and all downstream audits/readbacks/evals pass. +- User explicitly instructed to skip Claude review; the frozen contract permits a typed `user_waiver`, which will be bound to the exact candidate subject before merge. + +## Promotion Filter + +Promote a candidate to `tasks/lessons.md`, `docs/researches/`, or harness asset files only when all three hold: hard to reverse, surprising without local context, and a real trade-off existed. If any one is missing, keep it in this notes file instead. + +## Promotion Candidates + +- Promote to `tasks/lessons.md` only after a repeated correction or failure pattern. +- Promote to `docs/researches/` only when it is durable repo knowledge with evidence. +- Promote to harness asset files only after verification across more than one task or fixture. diff --git a/tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md b/tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md new file mode 100644 index 00000000..6da8ab05 --- /dev/null +++ b/tasks/reviews/20260810-0228-projection-accepted-change-protocol.review.md @@ -0,0 +1,89 @@ +# Task Review: projection-accepted-change-protocol + +> **Status**: Passed +> **Plan**: plans/plan-20260810-0228-projection-accepted-change-protocol.md +> **Contract**: tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md +> **Notes File**: tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md +> **Checks File**: .ai/harness/checks/latest.json +> **Last Updated**: 2026-08-10 02:56 +> **Recommendation**: pass +> **Review Rubric Version**: 2 +> **Reviewed Subject SHA256**: sha256:a721a18dc97ea662704affaae64ed7d066080e94393f9299a46aeddf815aba66 +> **Reviewed Subject Scope**: normalized-final-content +> **Reviewed Target Revision**: 9e3c040eb285d21c040edfb2da8f39eb22fbe98f + +## Human Review Card + +- Verdict: pass through the contract-authorized typed user waiver; this is not represented as an external Claude pass. +- Change type: code-change +- Intended files changed: additive projection request contract, CLI provider bridge, regression tests, aligned 0.4.1 release surfaces, and workflow evidence. +- Actual files changed: 22 reviewed product/release paths plus plan, contract, notes, review, and deferred-ledger timestamp, all inside `allowed_paths`. +- Commands passed: focused contract and CLI tests, typecheck, full `bun run verify`, strict contract verification, and `verify-sprint --prepare-acceptance` under Node 24.19.0. +- Residual risks: npm publication and downstream repo-harness dependency cutover still require post-merge live registry/runtime readback. +- Reviewer action required: none; the owner explicitly waived Claude review and the exact-subject `user_waiver` AcceptanceReceipt verifies. +- Rollback: revert the 0.4.1 protocol commit before publication; after publication, ship a forward patch and keep exact dependency pins. + +## Mode Evidence + +- Selected route: shared projection protocol patch and release candidate. +- P1/P2/P3 evidence: P1 maps repo-harness provider to `ProjectionRequestV1` and archctx renderer; P2 traces request parse → snapshot assertion → accepted classification → refresh signal receipt; P3 preserves v1 identity and explicit human authority while rejecting inference/fallback. +- Root cause or plan evidence: `runProjectionProtocolCommand` previously called `buildArchitectureDocsProjection` without the already-supported accepted reference, so a major delta could not leave `human-action-required` through the stable protocol. + +## Verification Evidence + +- Waza `/check` run: skipped by explicit user instruction; no Claude/Waza verdict is claimed. +- Commands run: `bun run typecheck`; focused contract/CLI suites; `bun run verify`; `repo-harness run verify-sprint --prepare-acceptance`. +- Manual checks: verified Node 24.19.0 satisfies `>=24 <26`; host Node 26.5.0 is intentionally not used for archctx release gates. +- Supporting artifacts: `.ai/harness/checks/latest.json` and the typed AcceptanceReceipt in repo-harness gate authority. +- Implementation notes reviewed: `tasks/notes/20260810-0228-projection-accepted-change-protocol.notes.md`. +- Run snapshot: `.ai/harness/runs/run-20260810T025206-79421-20260810-0228-projection-accepted-change-protocol.json`. + +## Manual Check Evidence + +- Not applicable: the contract declares no non-built-in `manual_checks`. + +## Acceptance Receipt Projection + +> **Disposition**: user_waiver +> **Reviewer**: User +> **Source**: user-waiver +> **Actor**: ancienttwo +> **Reviewed Subject SHA256**: sha256:a721a18dc97ea662704affaae64ed7d066080e94393f9299a46aeddf815aba66 +> **Reviewed Subject Scope**: normalized-final-content +> **Reviewed Target Revision**: 9e3c040eb285d21c040edfb2da8f39eb22fbe98f +> **Verification Evidence SHA256**: sha256:0ad986c14e19aed2f329178b85a2b7b41bd0e206b0607a29974257d8fae298df +> **Issued At**: 2026-08-09T18:56:18.767Z + +- Summary: User explicitly instructed this release to skip Claude review after deterministic projection and release gates pass. +- Findings: none + +## Behavior Diff Notes + +- `acceptedChange` is absent-by-default, so existing request payloads retain their previous semantics and receipt identity. +- A canonical accepted reference produces `refresh-required`; absent acceptance produces `human-action-required`; malformed, partial, extra-property, unsupported, or non-canonical values fail closed. + +## Residual Risks / Follow-ups + +- Post-merge npm publication and repo-harness exact dependency/runtime readback remain delivery steps, not unverified claims in this review. + +## Scorecard + +| Dimension | Score | Notes | +|-----------|-------|-------| +| Functionality | 10/10 | Both unresolved and accepted stable-protocol paths are covered. | +| Product depth | 9/10 | Cross-repo human authority now reaches the existing refresh taxonomy. | +| Design quality | 9/10 | Additive v1 field; no fallback, inference, or duplicate semantic type. | +| Code quality | 9/10 | Three-layer validation and full verification pass. | + +## Failing Items + +- None. External Claude review was explicitly waived rather than synthesized. + +## Retest Steps + +- Re-run: `PATH=:$PATH repo-harness run verify-sprint --prepare-acceptance`. +- Re-check: `repo-harness run acceptance-receipt -- verify --contract tasks/contracts/20260810-0228-projection-accepted-change-protocol.contract.md --verification .ai/harness/checks/latest.json`. + +## Summary + +- The 0.4.1 candidate is machine-verified and accepted by an exact-subject typed user waiver. It is ready for PR/merge; publication and repo-harness cutover follow from merged `main`. diff --git a/tasks/todos.md b/tasks/todos.md index 9a2c24f4..4af3105e 100644 --- a/tasks/todos.md +++ b/tasks/todos.md @@ -1,7 +1,7 @@ # Deferred Goal Ledger > **Status**: Backlog -> **Updated**: 2026-08-09 23:08 +> **Updated**: 2026-08-10 02:35 > **Scope**: Medium/long-term goals deferred from active plan execution Current plan tasks live in the active plan's `## Task Breakdown`.