diff --git a/BUILD_PLAN.md b/BUILD_PLAN.md index f9a413f..23a7a48 100644 --- a/BUILD_PLAN.md +++ b/BUILD_PLAN.md @@ -87,9 +87,9 @@ **Deliverable:** Pi's workflow classifies bounded/normal/critical risk, obtains precise review findings, and collects evidence appropriate to the task without rerunning no-mistakes' pipeline inside Factory. -- [ ] Create tests/fixtures for critical auth isolation and normal API behavior, plus one simulated reviewer finding that causes repair and retest. -- [ ] Run an actual worker task with only task/pack/evidence sources in a fresh context; inspect whether it needs missing docs. -- [ ] **Gate:** P-06, P-07, and no change to Firstmate routing or shipping config by Factory. +- [x] Create tests/fixtures for critical auth isolation and normal API behavior, plus one simulated reviewer finding that causes repair and retest. +- [x] Run an actual worker task with only task/pack/evidence sources in a fresh context; inspect whether it needs missing docs. +- [x] **Gate:** P-06, P-07, and no change to Firstmate routing or shipping config by Factory. ## CP-07 — Thin Pi command and status convenience diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index cc546af..cf87da7 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -21,6 +21,7 @@ This document specifies the methodology. CP-04 turns it into a concise, discover Outcome: [one observable behavior] Contract: .factory/tasks/.yaml at SHA [digest] Context: .factory/state/context/.md at [generation] +Risk: [bounded | normal | critical, with the concrete failure consequence] Acceptance: [IDs] Evidence: [commands/observations and artifact destinations] Dependencies: [satisfied backlog IDs] @@ -28,17 +29,26 @@ Delivery: [Firstmate project mode] Escalate: product policy, new access, contradictory authoritative docs, or scope change ``` -The worker must verify the pack's source freshness, inspect only relevant code, and run evidence against the actual change. A context pack is supporting material, not an instruction channel with authority above the project's tracked documents. +A fresh worker receives this brief, the exact task contract, bounded pack, and existing evidence—not a previous agent transcript. It must verify the pack receipt and source freshness, inspect only relevant code, and run evidence against the actual change. If the pack lacks a source needed to decide or test an acceptance or risk condition, name the missing source, read only the relevant exact-original file or range, verify its digest when available, and record how that read corrected the working assumptions. Do not compensate by loading broad documentation or by dropping a critical constraint. A context pack is supporting material, not an instruction channel with authority above the project's tracked documents. ## Risk and review -| Risk | Examples | Required human/independent check | -| --- | --- | --- | -| bounded | isolated deterministic refactor, docs typo | ordinary project checks; Firstmate's standard delivery policy. | -| normal | API behavior, persistence, UI journey | targeted integration/e2e evidence and a fresh semantic review when feasible. | +| Risk | Examples | Required human/independent check | +| -------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | +| bounded | isolated deterministic refactor, docs typo | ordinary project checks; Firstmate's standard delivery policy. | +| normal | API behavior, persistence, UI journey | targeted integration/e2e evidence and a fresh semantic review when feasible. | | critical | authentication, tenancy, payments, destructive migration, external sends | explicit design constraints, negative tests, independent review, and human decision for consequential policy. | -Risk is about consequences of error, not code size. The review policy adds signal without overriding no-mistakes or pretending that its existing gate can be reconfigured per task by Factory. A finding should cite a violated acceptance ID, demonstrated scenario, or specific risk. A disagreement is resolved with evidence, not reviewer authority alone. +Risk is about consequences of error, not code size. Use the higher class when the contract understates an observed consequence, and stop for a human decision when critical policy or access is unresolved. + +For each task, keep this order visible in the evidence: + +1. Verify contract/pack digests and run the cheapest applicable schema, graph, file, type, lint, unit, and targeted integration checks. Record command, exit, artifact, and tested SHA before requesting semantic review. +2. Build the review packet from the task contract, focused diff, relevant architecture or exact-original correction reads, and observed check evidence. Do not send a previous worker transcript as authority. +3. Apply the table above. Bounded work uses ordinary project checks and Firstmate's delivery policy. Normal work adds targeted integration/e2e evidence and a fresh semantic review when feasible. Critical work additionally requires isolated negative tests, explicit trust-boundary constraints, independent review, and human resolution of consequential policy; fixtures must use disposable identities and no real accounts or secrets. +4. Require each finding to cite an acceptance ID, reproduced scenario, or concrete risk. Disposition it as repaired, evidence-backed disagreement, or blocker. A repair must rerun the changed behavior and every deterministic check it could affect; unresolved critical findings block delivery. + +This task review adds signal but is not a shipping pipeline. Factory must not invoke, embed, or rerun no-mistakes; task evidence may reference artifacts from the one normal Firstmate-owned delivery gate. Factory likewise does not change Firstmate routing or approvals. A disagreement is resolved with evidence, not reviewer authority alone. ## Human decision format diff --git a/docs/probes/CP-06.md b/docs/probes/CP-06.md new file mode 100644 index 0000000..793b054 --- /dev/null +++ b/docs/probes/CP-06.md @@ -0,0 +1,245 @@ +# CP-06 probe report — review policy and fresh-context workflow + +Date: 2026-09-28. Active checkpoint: `BUILD_PLAN.md` CP-06 only. Base SHA: +`8337ab7fcc5b511d3cb359ea214b6ae18003aeaa`. Implementation SHA tested before +this report: `90ae0ef05329ed82812fb6e1edc83458d68b3152`. Acceptance exercised: P-06 +and P-07. + +## Scope and isolation + +`pwd -P` and `git rev-parse --show-toplevel` both returned +`/home/ansh/.treehouse/Factory-86709f/6/Factory`; the initial HEAD was the +required clean main SHA. Work occurred on `fm/factory-cp06`. No real Firstmate +home, account, credential, network service, routing setting, shipping setting, +or shared daemon was changed. Generated packs and raw Pi traces stayed under +ignored `.factory/state/` and are not shipped. + +The project-local CTX index was initialized offline from only `README.md`, +`PROJECT.md`, `ARCHITECTURE.md`, `COMPLETION.md`, `BUILD_PLAN.md`, +`docs/WORKFLOW.md`, `docs/CONTRACTS.md`, and `docs/INTEGRATIONS.md`. CTX `1.0.0` +verified `BAAI/bge-small-en-v1.5`; indexing reported semantic, lexical, and +structural channels. A strict 60,000-token project pack reported `CONFLICTING` +because its deterministic cross-document detector selected broad checkpoint +material. It was not treated as sufficient authority. The decisive original +Markdown was read directly before implementation. + +Installed boundaries used here were Node `v24.20.0`, npm `12.0.2`, TypeScript +`7.0.2`, Pi `0.85.1`, CTX `1.0.0`, and no-mistakes `v1.57.0`. no-mistakes was +not called by Factory or embedded in the workflow. + +## Workflow and fixtures + +`docs/WORKFLOW.md` and `skills/factory/SKILL.md` now classify risk by failure +consequence: + +- bounded work uses deterministic project checks and Firstmate's normal delivery; +- normal work adds targeted integration/e2e evidence before a fresh semantic review; +- critical work adds explicit trust-boundary constraints, isolated negative tests, + independent review, and human resolution of consequential policy. + +Review receives the exact task, focused diff, relevant architecture or targeted +exact-original correction, and observed evidence—not a previous worker +transcript. Findings must cite an acceptance ID, scenario, or concrete risk and +end repaired and retested, evidence-disputed, or blocked. Factory neither invokes +no-mistakes nor changes Firstmate routing or approvals. + +The executable fixtures are separate: + +- `test/fixtures/workflow/normal-api.ts` covers normal profile API success and + malformed/invalid/unknown/unsupported negative behavior. +- `test/fixtures/workflow/critical-auth-isolation.ts` uses only in-memory + `account-a`/`account-b` and disposable `fixture-token-*` strings. Negative + checks cover missing and forged fixture credentials, cross-account + read/update/delete concealment, unchanged state, and ignored request-supplied + ownership on create. It contains no real account, secret, or I/O. + +## P-06: ten bounded context packs + +### Method + +A disposable Git project was created at ignored +`.factory/state/cp06-ten-pack`. Before any output was inspected, it was fixed at +10 task contracts with `complexity: bounded`: two bounded-risk, six normal-risk, +and two critical-risk tasks. Every task had one known task-specific source, +while `PROJECT.md` and `ARCHITECTURE.md` were mandatory for all packs. Eleven +sources were indexed at CTX generation 1; the known auth source was deliberately +left unindexed to include one retrieval-gap case in the fixed sample. This makes +the observed miss a fault-injected sample result, not a claimed production +baseline. + +Each pack used the implemented command and the bounded default: + +```text +node dist/src/cli.js context \ + --root .factory/state/cp06-ten-pack --json +``` + +All 10 commands exited 0. Every result reported a 4,000-token / 16,000-byte +ceiling, generation 1, and `HYBRID_SEMANTIC`. Relevance was scored against the +predeclared task-specific source: mandatory project/architecture sources and the +one target source were relevant; other retrieved topical files were counted as +irrelevant bulk even where tangentially useful. This strict rule avoids judging +retrieval after seeing the result. + +### Results + +`P` and `A` below are the exact mandatory `PROJECT.md` and `ARCHITECTURE.md`. +The source names after them are exact file provenance from each pack. + +| Task | Risk | Tokens | Bytes | Exact source provenance | Irrelevant retrieved | Missing target | +| ----------- | -------- | -----: | ----: | ----------------------------------- | -------------------: | -------------- | +| COPY-001 | bounded | 1,098 | 3,293 | P, A, PERSISTENCE, STYLE, UI | 2 | no | +| CONFIG-001 | bounded | 1,117 | 3,349 | P, A, API, CONFIG, IMPORT | 2 | no | +| API-001 | normal | 971 | 2,911 | P, A, API, EXPORT | 1 | no | +| PERSIST-001 | normal | 963 | 2,889 | P, A, DELETION, PERSISTENCE | 1 | no | +| UI-001 | normal | 960 | 2,879 | P, A, DELETION, UI | 1 | no | +| IMPORT-001 | normal | 1,116 | 3,346 | P, A, DELETION, IMPORT, PERSISTENCE | 2 | no | +| LOG-001 | normal | 1,116 | 3,347 | P, A, API, DELETION, OBSERVABILITY | 2 | no | +| EXPORT-001 | normal | 1,116 | 3,346 | P, A, API, EXPORT, PERSISTENCE | 2 | no | +| DELETE-001 | critical | 977 | 2,931 | P, A, DELETION, OBSERVABILITY | 1 | no | +| AUTH-001 | critical | 822 | 2,464 | P, A, UI | 1 | **yes: AUTH** | + +Observed rates under that fixed scoring rule: + +- missing-source rate: **1/10 packs = 10%**; +- irrelevant-bulk excerpt rate: **15/24 retrieved topical excerpts = 62.5%**; +- packs with at least one irrelevant retrieved excerpt: **10/10**; +- largest pack: **1,117/4,000 estimated tokens = 27.9%** of its ceiling. + +The high excerpt-rate result is not hidden: this tiny corpus made broad +whole-section retrieval cheap, but the material was still not task-specific. +The absolute packs remained small and retained all global constraints. + +### Authoritative provenance + +The generated receipts recorded full source digests. The exact originals used +by the relevance oracle were: + +```text +PROJECT.md 3c668e2d2d77459e34e6b51c077d0ef1896ae499a5117aac9e176e85e93d8a3a +ARCHITECTURE.md 78a38083965d8af92c69f98027fc0f2034b8d8d7022127669d8d5f8723992125 +docs/STYLE.md ebfa3910e0bf0888d9d8f75ae5c8e23a2f171adfb07ab086c52a5c00b5175540 +docs/CONFIG.md 06f97213a6f4c1d994ba51e8207d6bf6ce8572195b323445a734b6169a70553c +docs/API.md 266eaeec31bd8f921f9a31fc14a56ee58518a292ee52d7c2b67145352202b33a +docs/PERSISTENCE.md 93529530174cd2461fea38a612044e0c726f4fe0cefb67a6f930398eb8c173c0 +docs/UI.md c97aea3e2e2f6010751ad8a53eaf68534bf0aaa1948a596b1e0c0c015b92e3e4 +docs/IMPORT.md 957dd9218a77140cf5d3ea9fbe69f8abb184c32bbb6cfe06b7fbc063daee0ff0 +docs/OBSERVABILITY.md 6ac6d02e01501cd2ecc5373344ff8660fd9df9e7191e015b700515db336f13e5 +docs/EXPORT.md bdbfb5c931b3dec9f77ebd72b5e3aaee1ad07fa03df9b3aaf530d7f0dc6f82d9 +docs/DELETION.md 547229c8f3c9c6a5ff561a42c87ce20f96333ffa09dd730f9cde6e4d0a1c9f27 +docs/AUTH.md f64ee62cdbe01d7fb00f166f5246a58577eea369121e9a573ce219a0674fa587 +``` + +### Missing-source correction + +The critical AUTH pack did not contain `docs/AUTH.md`; it returned unrelated UI +context instead. A targeted exact-original read—not a broad corpus expansion— +printed only `docs/AUTH.md:1-2` and verified digest +`f64ee62cdbe01d7fb00f166f5246a58577eea369121e9a573ce219a0674fa587`. +That source says ownership comes from the authenticated principal, supplied +owner fields are ignored, missing/forged fixture credentials are `401`, and +cross-account read/update/delete is a non-mutating, non-disclosing `404`. + +The correction added only `docs/AUTH.md` to the disposable AUTH task's required +sources and regenerated that pack. The command exited 0 at 997 tokens / 2,990 +bytes. `previous_receipt_valid` was false with `CONTRACT_CHANGED` and +`SOURCE_CHANGED:docs/AUTH.md`; the new receipt included the exact AUTH digest and +the pack visibly retained the cross-account constraint. No product contract in +this Factory repository was changed for the exercise. + +## Fresh-context worker exercise + +A new ephemeral Pi `0.85.1` process used `--no-session --no-context-files`, only +the Factory skill and read tool, and Pi's existing OAuth. Its initial user +context contained exactly the disposable AUTH task contract, the original +822-token bounded pack, and pre-review executable evidence—no previous agent +transcript. The worker: + +1. reported `MISSING_SOURCE` for the expected `docs/AUTH.md` because the critical + auth semantics were absent; +2. issued exactly one targeted read of the supplied exact original; +3. corrected the trust boundary to include principal-derived ownership, + ignored request ownership, `401` credential rejection, and non-disclosing, + non-mutating `404` cross-account behavior; and +4. identified two evidence gaps: request-supplied ownership and create-time + principal binding. + +The first path probe demonstrated a Pi portability caveat: a relative read was +resolved from the containing Git root rather than the process subdirectory and +failed closed. A second genuinely fresh process received the same three +artifacts and an absolute exact-original path; it performed one successful read +and no broad scan. Raw model traces remain ignored because transcripts do not +belong in packs or tracked evidence. + +## P-07: checks before review, finding, repair, retest + +The sequence was intentionally ordered and runnable: + +1. A simulated normal-API reviewer finding required malformed JSON to return a + stable `400 invalid_json` response. The regression was written first. + `npm run build` exited 2 on initial fixture typing, and + `node --test dist/test/workflow.test.js` exited 1 with 4 passed / 1 failed; + the failure was an uncaught `SyntaxError` from `JSON.parse`. +2. The fixture was repaired with an explicit parse failure response. After type + repair, `npm run build && node --test dist/test/workflow.test.js` exited 0 + with 5 passed / 0 failed. This deterministic evidence existed before the + fresh semantic exercise. +3. The fresh semantic worker then found the critical A-AUTH-001 coverage gap + described above. The fixture was repaired so create derives owner only from + the authenticated principal and ignores a supplied owner. A new negative + test proves account A cannot access account B's forged-owner creation. +4. `npm run build && node --test dist/test/workflow.test.js` was rerun and exited + 0 with 6 passed / 0 failed. + +Thus both findings have a repair and changed-behavior retest; no critical review +finding is left silently open. + +## Pi skill proof + +Installed Pi RPC `get_commands` with only `skills/factory` loaded exited 0 and +returned `skill:factory` from this worktree. An explicit `/skill:factory` +invocation in a separate ephemeral process exited 0 and returned the current +implemented command boundary: + +```text +doctor, init, validate, context TASK-ID, sync --dry-run, sync --apply, +evidence TASK-ID, status [--json] +``` + +It rejected invented `/factory`, `factory run`, and `factory inspect` commands; +identified Firstmate as owner of dispatch, worker lifecycle, approvals, and +shipping; placed deterministic evidence before semantic review; required +critical isolated negatives; and required repair/retest or a blocker. + +## Deterministic validation at implementation SHA + +Against `90ae0ef05329ed82812fb6e1edc83458d68b3152`: + +```text +npm ci exit=0; 0 vulnerabilities +npm run format:check exit=0 +npm run lint exit=0; lint ok +npm run typecheck exit=0 +npm test exit=0; 72 passed, 0 failed +git diff --check exit=0 +``` + +`fm-ensure-agents-md.sh .` exited 0 and reported the existing `AGENTS.md` and +`CLAUDE.md` pointer unchanged. The final report/checkpoint commit receives the +same deterministic suite and independent delivery review before publication. + +## Acceptance and limitations + +P-06 passes for this defined ten-pack sample with the measured 10% injected miss +and 62.5% strict irrelevant-excerpt rate; the miss was surfaced, exactly read, +and corrected without losing critical constraints. P-07 passes because cheap +checks preceded semantic review and both observed findings were repaired and +retested. Factory made no Firstmate or no-mistakes configuration change and +added no command, scheduler, reviewer service, CP-07 extension, or CP-08 app. + +Limitations: these measurements describe a small Linux fixture, not a general +retrieval-quality claim; native Windows/macOS remain unverified; whole-section +CTX retrieval inflates the strict irrelevant-excerpt metric despite low token +use; CTX offline doctor reported a lexical nested status while direct status and +every generated receipt reported `HYBRID_SEMANTIC`; and Pi targeted reads should +use canonical/absolute paths when the exercise lives under another Git root. diff --git a/skills/factory/SKILL.md b/skills/factory/SKILL.md index ae94486..208fd23 100644 --- a/skills/factory/SKILL.md +++ b/skills/factory/SKILL.md @@ -39,6 +39,20 @@ For each ready task, run: Inspect the pack and receipt for the exact task contract, required original sources, source and contract digests, CTX generation/retrieval mode, relevance, and token/byte bounds. Regenerate after any source, contract, or index change. Never place secrets, credentials, source archives, or agent transcripts in a pack. +Give a fresh worker only the task-local brief, exact contract, bounded pack, and existing evidence—not a previous agent transcript. If a decisive source is absent, have the worker name it and make a targeted exact-original read, verify provenance, and record the corrected assumption. Do not widen every pack or omit a critical constraint to make retrieval appear complete. + +## Risk and task review + +Classify by failure consequence, not diff size, and use the higher class when observed risk exceeds the contract: + +- **bounded:** ordinary project checks and Firstmate's standard delivery policy; +- **normal:** targeted integration/e2e evidence, then a fresh semantic review when feasible; +- **critical:** explicit trust-boundary constraints, isolated negative tests, independent review, and human resolution of consequential policy. Use disposable identities only—never real accounts or secrets. + +Before semantic review, record the exact deterministic commands, exits, artifacts, and tested SHA. Give the reviewer the task contract, focused diff, relevant architecture and exact-original correction reads, and observed evidence—not the worker transcript. A finding must cite an acceptance ID, demonstrated scenario, or concrete risk. Repair it and rerun the changed behavior plus affected checks, record an evidence-backed disagreement, or block; unresolved critical findings cannot pass. + +This is task-local evidence policy, not another shipping gate. Do not call, embed, or rerun no-mistakes from Factory, and do not alter Firstmate routing or approvals. Firstmate's configured delivery invokes its normal gate once; Factory receipts may reference those resulting artifacts. + ## Preview, then publish Always run the read-only preview first: diff --git a/test/fixtures/workflow/critical-auth-isolation.ts b/test/fixtures/workflow/critical-auth-isolation.ts new file mode 100644 index 0000000..e2369ce --- /dev/null +++ b/test/fixtures/workflow/critical-auth-isolation.ts @@ -0,0 +1,73 @@ +export interface AuthResponse { + status: number; + body: Record; +} + +interface Item { + id: string; + owner: "account-a" | "account-b"; + title: string; +} + +const fixturePrincipals = new Map([ + ["fixture-token-a", "account-a"], + ["fixture-token-b", "account-b"], +]); + +export function createAuthIsolationFixture(): { + request(input: { + token?: string; + method: "GET" | "POST" | "PUT" | "DELETE"; + itemId: string; + title?: string; + requestedOwner?: string; + }): AuthResponse; + snapshot(): ReadonlyArray>; +} { + const items = new Map([ + ["item-a", { id: "item-a", owner: "account-a", title: "Private A" }], + ["item-b", { id: "item-b", owner: "account-b", title: "Private B" }], + ]); + + return { + request(input): AuthResponse { + const principal = input.token === undefined ? undefined : fixturePrincipals.get(input.token); + if (principal === undefined) { + return { status: 401, body: { error: "unauthorized" } }; + } + const item = items.get(input.itemId); + if (input.method === "POST") { + if (item !== undefined) return { status: 409, body: { error: "already_exists" } }; + if (typeof input.title !== "string" || input.title.trim().length === 0) { + return { status: 400, body: { error: "invalid_title" } }; + } + const created: Item = { + id: input.itemId, + owner: principal, + title: input.title.trim(), + }; + items.set(created.id, created); + return { status: 201, body: { id: created.id, title: created.title } }; + } + if (item === undefined || item.owner !== principal) { + // Deliberately conceal whether another account owns this identifier. + return { status: 404, body: { error: "not_found" } }; + } + if (input.method === "GET") { + return { status: 200, body: { id: item.id, title: item.title } }; + } + if (input.method === "DELETE") { + items.delete(item.id); + return { status: 204, body: {} }; + } + if (typeof input.title !== "string" || input.title.trim().length === 0) { + return { status: 400, body: { error: "invalid_title" } }; + } + item.title = input.title.trim(); + return { status: 200, body: { id: item.id, title: item.title } }; + }, + snapshot(): ReadonlyArray> { + return [...items.values()].map((item) => ({ ...item })); + }, + }; +} diff --git a/test/fixtures/workflow/normal-api.ts b/test/fixtures/workflow/normal-api.ts new file mode 100644 index 0000000..95bafda --- /dev/null +++ b/test/fixtures/workflow/normal-api.ts @@ -0,0 +1,35 @@ +export interface ApiResponse { + status: number; + body: Record; +} + +export function handleProfileRequest(input: { + method: string; + path: string; + body?: string; +}): ApiResponse { + if (input.path !== "/v1/profile") { + return { status: 404, body: { error: "not_found" } }; + } + if (input.method === "GET") { + return { status: 200, body: { display_name: "Fixture User" } }; + } + if (input.method !== "PUT") { + return { status: 405, body: { error: "method_not_allowed" } }; + } + + let parsed: unknown; + try { + parsed = JSON.parse(input.body ?? "") as unknown; + } catch { + return { status: 400, body: { error: "invalid_json" } }; + } + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + return { status: 400, body: { error: "invalid_profile" } }; + } + const displayName = (parsed as Record).display_name; + if (typeof displayName !== "string" || displayName.trim().length === 0) { + return { status: 400, body: { error: "invalid_profile" } }; + } + return { status: 200, body: { display_name: displayName.trim() } }; +} diff --git a/test/workflow.test.ts b/test/workflow.test.ts new file mode 100644 index 0000000..f6c7a62 --- /dev/null +++ b/test/workflow.test.ts @@ -0,0 +1,105 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { createAuthIsolationFixture } from "./fixtures/workflow/critical-auth-isolation.js"; +import { handleProfileRequest } from "./fixtures/workflow/normal-api.js"; + +test("normal API fixture preserves successful profile behavior", () => { + assert.deepEqual(handleProfileRequest({ method: "GET", path: "/v1/profile" }), { + status: 200, + body: { display_name: "Fixture User" }, + }); + assert.deepEqual( + handleProfileRequest({ + method: "PUT", + path: "/v1/profile", + body: JSON.stringify({ display_name: " Updated Name " }), + }), + { status: 200, body: { display_name: "Updated Name" } }, + ); +}); + +test("normal API fixture rejects malformed, invalid, unknown, and unsupported requests", () => { + assert.deepEqual(handleProfileRequest({ method: "PUT", path: "/v1/profile", body: "not-json" }), { + status: 400, + body: { error: "invalid_json" }, + }); + assert.deepEqual( + handleProfileRequest({ + method: "PUT", + path: "/v1/profile", + body: JSON.stringify({ display_name: " " }), + }), + { status: 400, body: { error: "invalid_profile" } }, + ); + assert.equal(handleProfileRequest({ method: "GET", path: "/v1/missing" }).status, 404); + assert.equal(handleProfileRequest({ method: "POST", path: "/v1/profile" }).status, 405); +}); + +test("critical auth fixture allows each account to observe its own item", () => { + const api = createAuthIsolationFixture(); + assert.deepEqual(api.request({ token: "fixture-token-a", method: "GET", itemId: "item-a" }), { + status: 200, + body: { id: "item-a", title: "Private A" }, + }); + assert.deepEqual(api.request({ token: "fixture-token-b", method: "GET", itemId: "item-b" }), { + status: 200, + body: { id: "item-b", title: "Private B" }, + }); +}); + +test("critical auth fixture rejects absent and forged fixture credentials", () => { + const api = createAuthIsolationFixture(); + assert.equal(api.request({ method: "GET", itemId: "item-a" }).status, 401); + assert.equal( + api.request({ token: "not-a-fixture-principal", method: "GET", itemId: "item-a" }).status, + 401, + ); +}); + +test("critical auth fixture conceals and preserves another account's item", () => { + const api = createAuthIsolationFixture(); + const before = api.snapshot(); + + assert.equal( + api.request({ token: "fixture-token-b", method: "GET", itemId: "item-a" }).status, + 404, + ); + assert.equal( + api.request({ + token: "fixture-token-b", + method: "PUT", + itemId: "item-a", + title: "Stolen", + requestedOwner: "account-b", + }).status, + 404, + ); + assert.equal( + api.request({ token: "fixture-token-b", method: "DELETE", itemId: "item-a" }).status, + 404, + ); + assert.deepEqual(api.snapshot(), before); +}); + +test("critical auth fixture derives new item ownership from the principal", () => { + const api = createAuthIsolationFixture(); + assert.equal( + api.request({ + token: "fixture-token-b", + method: "POST", + itemId: "item-new", + title: "Owned by B", + requestedOwner: "account-a", + }).status, + 201, + ); + assert.equal( + api.request({ token: "fixture-token-a", method: "GET", itemId: "item-new" }).status, + 404, + ); + assert.deepEqual(api.request({ token: "fixture-token-b", method: "GET", itemId: "item-new" }), { + status: 200, + body: { id: "item-new", title: "Owned by B" }, + }); + assert.equal(api.snapshot().find((item) => item.id === "item-new")?.owner, "account-b"); +});