docs(01): plan phase 1 — 7 plans across 3 waves, verified after 1 revision
Wave 1: Plan 01 (scaffold + test infra)
Wave 2: Plans 02 (eslint firewall), 03 (save layer), 04 (content pipeline),
05 (asset provenance — autonomous:false human-curate checkpoint),
06 (doctrine docs)
Wave 3: Plan 07 (CI workflow)
All 16 Phase-1 REQ-IDs covered. Plan-checker found 4 blockers + 6 warnings
on first pass; revision iteration 1 landed all 10 fixes; iteration 2
returned VERIFICATION PASSED. Two orchestrator judgment calls during
revision: (1) implement CORE-04 localStorage fallback in Phase 1 (the
literal requirement and ROADMAP success criterion #2 both call for it),
(2) reclassify STRY-09 as vacuously satisfied in Phase 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
---
|
||||
phase: 01
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [01-01]
|
||||
files_modified:
|
||||
- scripts/validate-assets.mjs
|
||||
- scripts/validate-assets.test.ts
|
||||
- assets/north-stars/.gitkeep
|
||||
- assets/north-stars/README.md
|
||||
- assets/__samples__/refused/no-provenance.png
|
||||
- assets/__samples__/refused/.gitkeep
|
||||
autonomous: false
|
||||
requirements: [AEST-08, AEST-09, PIPE-03]
|
||||
must_haves:
|
||||
truths:
|
||||
- "`node scripts/validate-assets.mjs` exits 0 when every asset under `/assets/` (excluding `__samples__/refused/`) carries a sibling `<filename>.provenance.json` validating against the Zod sidecar schema"
|
||||
- "The validator script exits non-zero with a clear error message when any asset under `/assets/` (excluding refused/) lacks a sidecar (proving the gate works — AEST-09 + PIPE-03)"
|
||||
- "The provenance schema enforces all 6 required fields per CLAUDE.md / AEST-08: `model_id`, `checkpoint_hash`, `prompt`, `seed`, `sampler`, `params`, plus an optional `provenance_schema_version: number` for forward-compat (per RESEARCH Open Question #2)"
|
||||
- "10–20 hand-curated north-star reference images are committed under `assets/north-stars/` with valid provenance sidecars (CONTEXT D-01) — OR — if no AI tool is available, a documented fallback set with provenance fields filled honestly (`model_id: 'human'`, etc., per RESEARCH Open Question #5 + Environment Availability fallback) is committed"
|
||||
- "A refused-sample asset under `assets/__samples__/refused/no-provenance.png` proves the gate by being explicitly excluded from validation (CONTEXT D-03)"
|
||||
- "A Vitest test runs the validator script against an isolated `os.tmpdir()` fixture directory containing a deliberately-missing-provenance file and asserts the script exits non-zero, with no risk of polluting the real `/assets/` tree"
|
||||
artifacts:
|
||||
- path: scripts/validate-assets.mjs
|
||||
provides: "Standalone Node script (~30 lines) that walks /assets/ (or whatever ASSETS_DIR points at), pairs each non-sidecar non-.gitkeep file with <filename>.provenance.json, validates against ProvenanceSchema, exits non-zero on missing/invalid"
|
||||
- path: scripts/validate-assets.test.ts
|
||||
provides: "Vitest integration test that creates an isolated per-run fixture under os.tmpdir(), runs the validator with ASSETS_DIR pointing at the tmpdir as a subprocess, asserts exit code"
|
||||
- path: assets/north-stars/README.md
|
||||
provides: "Explains the north-star reference set: what these images are, how to add new ones, the sidecar naming convention"
|
||||
- path: assets/__samples__/refused/no-provenance.png
|
||||
provides: "Sample image with NO sidecar; validator must exclude this directory; existence proves the gate works (CONTEXT D-03)"
|
||||
- path: "assets/north-stars/*.png"
|
||||
provides: "10–20 hand-curated reference images establishing the watercolor visual north-star (CONTEXT D-01)"
|
||||
key_links:
|
||||
- from: scripts/validate-assets.mjs
|
||||
to: "assets/**/*"
|
||||
via: "node:fs/promises readdir + sibling sidecar lookup"
|
||||
pattern: "readdir.*assets|walk\\(ASSETS\\)"
|
||||
- from: scripts/validate-assets.mjs
|
||||
to: "assets/__samples__/refused/"
|
||||
via: "Hardcoded REFUSED exclusion list"
|
||||
pattern: "REFUSED|__samples__/refused"
|
||||
- from: scripts/validate-assets.test.ts
|
||||
to: scripts/validate-assets.mjs
|
||||
via: "child_process.execFile against the script with ASSETS_DIR=<os.tmpdir()/...> + assert exit code"
|
||||
pattern: "spawn\\(.*node.*validate-assets|execFile|os\\.tmpdir"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the AI asset provenance pipeline floor: a 30-line standalone Node script (`scripts/validate-assets.mjs`) that walks `/assets/` (or whatever `ASSETS_DIR` env var points at), validates each asset has a sibling `<filename>.provenance.json` file matching a Zod schema covering the 6 required fields per CLAUDE.md + AEST-08 (`model_id`, `checkpoint_hash`, `prompt`, `seed`, `sampler`, `params`) plus an optional forward-compat `provenance_schema_version` field. Excludes `assets/__samples__/refused/` so a sample sidecarless image can prove the gate exists. Commits 10–20 hand-curated north-star reference images establishing the visual style (watercolor; real-but-slightly-wrong flora) with provenance sidecars per CONTEXT D-01 — OR — if no AI tool is locally available, a documented fallback per RESEARCH Open Question #5. Ships a Vitest test that programmatically asserts the validator exits non-zero on a fixture missing provenance (PIPE-03 + AEST-09), with the negative-case fixture parked under `os.tmpdir()` so it never pollutes the real `/assets/` tree.
|
||||
|
||||
Purpose: This is the floor of the asset pipeline that Phase 5 will scale up to production volume. CONTEXT D-01/D-02/D-03 lock the shape (sidecar + CI walker, no curator workflow, no two-stage promotion). The 10–20 reference set is the seed against which Phase 5+ asset migrations will be visually regressed. RESEARCH § Pattern 6 provides verbatim code.
|
||||
|
||||
Output: A complete asset pipeline floor: validator script, sidecar schema, refused-sample fixture, ~10–20 north-star images with provenance sidecars, and a Vitest test enforcing the gate.
|
||||
|
||||
This plan is `autonomous: false` because Task 2 — committing the 10–20 north-star images — requires human curation per CONTEXT D-01 + D-03 (curation gate IS the human reviewer per CONTEXT). The user must approve which images ship. If the user has no local AI image tool, the fallback (commit licensed-CC-BY photographs of real cottage gardens or hand-painted references with provenance fields filled honestly) is acceptable per RESEARCH Open Question #5 + Environment Availability — but still needs human selection.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-foundations-and-doctrine/01-CONTEXT.md
|
||||
@.planning/phases/01-foundations-and-doctrine/01-RESEARCH.md
|
||||
@.planning/phases/01-foundations-and-doctrine/01-01-SUMMARY.md
|
||||
@CLAUDE.md
|
||||
@.planning/research/PITFALLS.md
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Provenance validator script + Zod sidecar schema + Vitest enforcement test</name>
|
||||
<files>
|
||||
scripts/validate-assets.mjs,
|
||||
scripts/validate-assets.test.ts,
|
||||
assets/__samples__/refused/no-provenance.png,
|
||||
assets/__samples__/refused/.gitkeep
|
||||
</files>
|
||||
<read_first>
|
||||
- .planning/phases/01-foundations-and-doctrine/01-RESEARCH.md § "Pattern 6: Provenance Sidecar Validator" (verbatim ~30-line script) and § "Provenance sidecar example" (the JSON shape) and § Open Question #2 (optional `provenance_schema_version` field)
|
||||
- .planning/phases/01-foundations-and-doctrine/01-CONTEXT.md (D-01 — full provenance metadata, 6 fields; D-02 — vendor deferred; D-03 — sidecar + CI walker, refused sample, no curator workflow)
|
||||
- CLAUDE.md "Code Style" — provenance metadata: `{model_id, checkpoint_hash, prompt, seed, sampler, params}`
|
||||
- REQUIREMENTS.md AEST-08 + AEST-09
|
||||
- package.json (Plan 01 already created the `validate:assets` script that calls this file)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- **validate-assets.mjs:**
|
||||
- Recursively walks `process.env.ASSETS_DIR ?? 'assets'` using `node:fs/promises` (Node 20+ supports `readdir({recursive: true, withFileTypes: true})`).
|
||||
- Skips any path under `assets/__samples__/refused/` (the gate proof — these files INTENTIONALLY have no sidecar).
|
||||
- Skips `.gitkeep` files.
|
||||
- Skips files ending in `.provenance.json` (those are sidecars, not assets).
|
||||
- For every other file, requires a sibling `<filename>.provenance.json` (e.g., `garden-soil-01.png` requires `garden-soil-01.png.provenance.json` per RESEARCH § Pattern 6 sidecar naming convention decision).
|
||||
- Reads each sidecar and validates against `ProvenanceSchema` (Zod with the 6 required fields + optional `provenance_schema_version`).
|
||||
- On any missing or invalid sidecar, prints a clear error and exits non-zero.
|
||||
- On success, prints `[provenance] all assets carry valid provenance.` and exits 0.
|
||||
- **validate-assets.test.ts:**
|
||||
- **Positive case:** Runs the validator against the real `/assets/` tree (the default — no `ASSETS_DIR` override) and asserts exit 0. This proves the north-star set + refused-sample dir together pass the gate.
|
||||
- **Negative case (BLOCKER 2 fix):** Generates a per-test-run unique tmpdir under `os.tmpdir()` (using `node:os` + `node:path` + `fs.mkdtemp`), drops a single PNG with no sidecar inside, runs the validator with `ASSETS_DIR=<that tmpdir>` set in the env, asserts exit code !== 0 and stderr/stdout contains the expected error message. Cleans up the tmpdir in `afterAll`. **No risk of polluting `/assets/`, no Ctrl-C cleanup hazard** — even if the test runner is killed mid-run, the OS reclaims the tmpdir on next reboot.
|
||||
</behavior>
|
||||
<action>
|
||||
**Step 1 — `scripts/validate-assets.mjs`** (per RESEARCH § Pattern 6 verbatim, with one improvement: the optional `provenance_schema_version` per Open Question #2):
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
// scripts/validate-assets.mjs — Phase 1 asset provenance gate (PIPE-03, AEST-08, AEST-09)
|
||||
//
|
||||
// Walks /assets/ (or process.env.ASSETS_DIR for tests), requires every non-sidecar
|
||||
// non-.gitkeep file to have a sibling <filename>.provenance.json validating against
|
||||
// ProvenanceSchema. Excludes /assets/__samples__/refused/ (which intentionally lacks
|
||||
// sidecars to prove the gate).
|
||||
//
|
||||
// Per CONTEXT D-03: minimum-viable. No curator workflow, no two-stage promotion,
|
||||
// no pre-commit hook. Sidecar + this script + CI is the entire pipeline.
|
||||
//
|
||||
// Per CONTEXT D-01: 6 required fields per CLAUDE.md provenance metadata.
|
||||
// Per RESEARCH Open Question #2: optional provenance_schema_version for Phase 5 fwd-compat.
|
||||
|
||||
import { readdir, readFile } from 'node:fs/promises';
|
||||
import { join, basename } from 'node:path';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ProvenanceSchema = z.object({
|
||||
model_id: z.string().min(1),
|
||||
checkpoint_hash: z.string().min(1),
|
||||
prompt: z.string().min(1),
|
||||
seed: z.union([z.string(), z.number()]),
|
||||
sampler: z.string().min(1),
|
||||
params: z.record(z.string(), z.unknown()),
|
||||
provenance_schema_version: z.number().int().positive().optional(),
|
||||
});
|
||||
|
||||
const ASSETS_DIR = process.env.ASSETS_DIR ?? 'assets';
|
||||
// Refused-sample exclusion is relative to the *real* assets tree; tests pointing
|
||||
// ASSETS_DIR at a tmpdir won't have these paths so the exclusion is harmless.
|
||||
const REFUSED_PREFIXES = ['assets/__samples__/refused', 'assets/__test_fixtures__/refused'];
|
||||
|
||||
async function* walk(dir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(dir, { withFileTypes: true });
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') return;
|
||||
throw e;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
yield* walk(path);
|
||||
} else {
|
||||
yield path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePath(p) {
|
||||
return p.replaceAll('\\', '/');
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
let assetCount = 0;
|
||||
|
||||
for await (const path of walk(ASSETS_DIR)) {
|
||||
const norm = normalizePath(path);
|
||||
if (REFUSED_PREFIXES.some((r) => norm.startsWith(r))) continue;
|
||||
if (norm.endsWith('.provenance.json')) continue;
|
||||
if (basename(norm) === '.gitkeep') continue;
|
||||
if (basename(norm) === 'README.md') continue;
|
||||
|
||||
assetCount++;
|
||||
const sidecar = path + '.provenance.json';
|
||||
try {
|
||||
const raw = await readFile(sidecar, 'utf8');
|
||||
const parsed = ProvenanceSchema.safeParse(JSON.parse(raw));
|
||||
if (!parsed.success) {
|
||||
errors.push(`${path}: provenance schema validation failed — ${parsed.error.message}`);
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push(`${path}: missing or unreadable provenance sidecar (${sidecar}): ${e.code ?? e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.error('[provenance] validation failed:');
|
||||
for (const err of errors) console.error(' ' + err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`[provenance] all ${assetCount} assets carry valid provenance.`);
|
||||
```
|
||||
|
||||
Note the `ASSETS_DIR` env override — this lets the Vitest test point the script at an `os.tmpdir()` fixture directory without modifying production code, **and without leaving stray fixture files in `/assets/`** (BLOCKER 2 fix). The `REFUSED_PREFIXES` list covers paths under the real `assets/` tree; tmpdir-based test runs simply have no such paths and the exclusion is a harmless no-op.
|
||||
|
||||
Also note: the script accepts `assets/north-stars/README.md` (skipped by `basename === 'README.md'`) — Task 2 will add this README; without the skip, the validator would demand a sidecar for the README itself.
|
||||
|
||||
**Step 2 — Make the script executable (Unix; harmless on Windows):**
|
||||
```bash
|
||||
chmod +x scripts/validate-assets.mjs 2>/dev/null || true
|
||||
```
|
||||
|
||||
**Step 3 — Create the refused-sample asset.** Per CONTEXT D-03, a real image file under `assets/__samples__/refused/` proves the gate exists by being intentionally without a sidecar. Use a tiny 1x1 transparent PNG (~70 bytes); generate with Node:
|
||||
```bash
|
||||
node -e "const fs = require('fs'); const png = Buffer.from('89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d49444154789c63600100000005000146cd9c5d0000000049454e44ae426082', 'hex'); fs.writeFileSync('assets/__samples__/refused/no-provenance.png', png);"
|
||||
touch assets/__samples__/refused/.gitkeep
|
||||
```
|
||||
The `.gitkeep` ensures the directory persists if the PNG is ever removed; the PNG itself is the gate-proof artifact.
|
||||
|
||||
**Step 4 — Run the validator manually:** `node scripts/validate-assets.mjs` should exit 0 (only the refused-sample is in `/assets/`, and it's excluded). Output: `[provenance] all 0 assets carry valid provenance.`
|
||||
|
||||
**Step 5 — `scripts/validate-assets.test.ts`** (Vitest integration test). The negative-case fixture is created under `os.tmpdir()` per BLOCKER 2 fix — isolated from the real `/assets/` tree, no orphan-fragility risk, no Ctrl-C cleanup hazard:
|
||||
```typescript
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { execFile } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
||||
const exec = promisify(execFile);
|
||||
const SCRIPT = 'scripts/validate-assets.mjs';
|
||||
|
||||
describe('PIPE-03 / AEST-09: asset provenance gate', () => {
|
||||
it('exits 0 against the real /assets/ tree (refused sample excluded)', async () => {
|
||||
const result = await exec('node', [SCRIPT]);
|
||||
expect(result.stdout).toMatch(/all \d+ assets carry valid provenance/);
|
||||
});
|
||||
|
||||
describe('with an isolated tmpdir fixture missing provenance', () => {
|
||||
let tmpDir: string;
|
||||
let fixtureFile: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Per-test-run unique tmpdir under os.tmpdir() — isolated from /assets/,
|
||||
// no risk of polluting the real tree even if the runner is killed mid-test.
|
||||
tmpDir = await mkdtemp(join(os.tmpdir(), 'tlg-provenance-test-'));
|
||||
fixtureFile = join(tmpDir, 'orphan.png');
|
||||
// Tiny 1x1 PNG with no sidecar
|
||||
const png = Buffer.from(
|
||||
'89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d49444154789c63600100000005000146cd9c5d0000000049454e44ae426082',
|
||||
'hex',
|
||||
);
|
||||
await writeFile(fixtureFile, png);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('exits non-zero with a clear error message when ASSETS_DIR points at the fixture', async () => {
|
||||
// Run the validator against the isolated tmpdir; the script reads ASSETS_DIR
|
||||
// from process.env, so the orphan.png is the only file under inspection.
|
||||
let exitCode = 0;
|
||||
let combinedOutput = '';
|
||||
try {
|
||||
await exec('node', [SCRIPT], { env: { ...process.env, ASSETS_DIR: tmpDir } });
|
||||
} catch (err: any) {
|
||||
exitCode = err.code ?? -1;
|
||||
combinedOutput = (err.stdout ?? '') + (err.stderr ?? '');
|
||||
}
|
||||
expect(exitCode).toBe(1);
|
||||
expect(combinedOutput).toMatch(/validation failed/);
|
||||
expect(combinedOutput).toMatch(/orphan\.png/);
|
||||
expect(combinedOutput).toMatch(/missing.*provenance sidecar/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Step 6 — Run `npm test` and confirm the validate-assets test passes.** The positive case asserts the real `/assets/` tree (refused-sample dir + Task 2's north-stars) passes the gate; the negative case runs the script against an isolated `os.tmpdir()` fixture with one orphan PNG and asserts exit 1 + the expected error message.
|
||||
|
||||
Per RESEARCH § Pattern 6 ("Refused-sample test"), the negative-case fixture proves the gate fires; the BLOCKER 2 fix moves that fixture to `os.tmpdir()` so it cannot pollute the real `/assets/` tree.
|
||||
|
||||
**Step 7 — Commit `feat(01-05): asset provenance validator + Zod sidecar schema + refused-sample fixture + PIPE-03 enforcement test (tmpdir-isolated)`.**
|
||||
</action>
|
||||
<verify>
|
||||
<automated>node scripts/validate-assets.mjs && npx vitest run scripts/validate-assets.test.ts</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `scripts/validate-assets.mjs` exists and is a runnable Node script — verify with `node --check scripts/validate-assets.mjs`.
|
||||
- The script defines a Zod `ProvenanceSchema` with all 6 CLAUDE.md fields plus optional `provenance_schema_version` — verify with `grep -cE "(model_id|checkpoint_hash|prompt|seed|sampler|params|provenance_schema_version)" scripts/validate-assets.mjs` returns at least 7.
|
||||
- The script reads from `process.env.ASSETS_DIR ?? 'assets'` (so the test can isolate via env override) — verify with `grep -q "process.env.ASSETS_DIR" scripts/validate-assets.mjs`.
|
||||
- The script excludes `__samples__/refused` — verify with `grep -q "__samples__/refused" scripts/validate-assets.mjs`.
|
||||
- The script exits non-zero on missing sidecar — verify with `grep -q "process.exit(1)" scripts/validate-assets.mjs`.
|
||||
- `assets/__samples__/refused/no-provenance.png` exists with no sidecar — verify with `test -f assets/__samples__/refused/no-provenance.png && ! test -f assets/__samples__/refused/no-provenance.png.provenance.json`.
|
||||
- **Test fixture isolation (BLOCKER 2):** `scripts/validate-assets.test.ts` uses `os.tmpdir()` for the negative-case fixture — verify with `grep -q "os.tmpdir" scripts/validate-assets.test.ts && grep -q "mkdtemp" scripts/validate-assets.test.ts`.
|
||||
- **Test fixture isolation (BLOCKER 2):** the negative-case test passes `ASSETS_DIR` via the `env` option of `execFile` — verify with `grep -E "ASSETS_DIR.*tmpDir|env:.*ASSETS_DIR" scripts/validate-assets.test.ts`.
|
||||
- **Test fixture isolation (BLOCKER 2):** no `assets/__test_fixtures__/missing` path is created during the test — verify with `! grep -q "assets/__test_fixtures__/missing" scripts/validate-assets.test.ts`.
|
||||
- Running the script directly exits 0: `node scripts/validate-assets.mjs` — exit code 0.
|
||||
- The Vitest test passes (positive + negative cases both green) — verify with `npx vitest run scripts/validate-assets.test.ts 2>&1 | grep -E "passed"` exits 0.
|
||||
- The Vitest test cleans up its tmpdir — verify with `grep -q "afterAll" scripts/validate-assets.test.ts && grep -q "rm.*tmpDir" scripts/validate-assets.test.ts`.
|
||||
</acceptance_criteria>
|
||||
<done>
|
||||
Validator script (~80 lines including error handling and Windows-path normalization) at `scripts/validate-assets.mjs`; Zod sidecar schema covering all 6 fields + optional schema version; refused-sample PNG committed under `__samples__/refused/`; Vitest test that creates an **isolated `os.tmpdir()` fixture** (BLOCKER 2 fix — no real-tree pollution risk), runs the validator with `ASSETS_DIR` pointing at the tmpdir, asserts non-zero exit + clear error message, cleans up; both `node scripts/validate-assets.mjs` and `npx vitest run scripts/validate-assets.test.ts` green; commit landed.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 2: Curate and commit 10–20 north-star reference images with provenance sidecars (CONTEXT D-01)</name>
|
||||
<what-built>
|
||||
Plan 05 Task 1 shipped:
|
||||
- `scripts/validate-assets.mjs` — the CI gate (exits non-zero on missing/invalid provenance)
|
||||
- `assets/__samples__/refused/no-provenance.png` — the proof-of-gate fixture
|
||||
- `scripts/validate-assets.test.ts` — Vitest test enforcing the gate (negative case isolated under `os.tmpdir()`)
|
||||
|
||||
Task 2 ships the 10–20 hand-curated north-star reference set per CONTEXT D-01 — the visual ground truth Phase 5+ will regenerate against. Per CONTEXT D-03, the curation gate IS the human reviewer (you), not a workflow document. This task pauses for your hands-on curation.
|
||||
|
||||
**Three valid paths** (your call):
|
||||
|
||||
**Path A — AI-generated (recommended if you have a tool available):**
|
||||
1. Use whatever AI image tool you currently have (Claude with image generation, Stable Diffusion + watercolor LoRA, Midjourney, Scenario, etc.).
|
||||
2. Generate 10–20 watercolor-style images representing the visual north-star: walled cottage gardens, real-but-slightly-wrong wildflowers, golden/autumnal palette for Season 1, hand-painted feel, no fantasy elements (no D&D flora — see PROJECT.md "Out of Scope").
|
||||
3. For each generated image, write a sibling `<filename>.png.provenance.json` with all 6 required fields filled honestly (the exact `model_id` you used, the prompt verbatim, the seed if your tool surfaces one, etc.).
|
||||
4. Place the pair under `assets/north-stars/<descriptive-slug>.png` + `assets/north-stars/<descriptive-slug>.png.provenance.json`.
|
||||
|
||||
**Path B — Hand-painted / photograph fallback (if no AI tool is available locally):**
|
||||
Per RESEARCH § Open Question #5 + Environment Availability, the provenance schema accepts arbitrary `model_id` strings, so honest "human-painted" or licensed-photograph entries are valid. For each image:
|
||||
- `model_id`: `"human"` (or `"photograph:cc-by:<photographer>"`)
|
||||
- `checkpoint_hash`: `"n/a"`
|
||||
- `prompt`: a description of what the image is
|
||||
- `seed`: `0`
|
||||
- `sampler`: `"n/a"`
|
||||
- `params`: `{ "notes": "Phase 1 fallback per RESEARCH Open Question #5; replaceable in Phase 5+" }`
|
||||
|
||||
**Path C — Defer with explicit IOU:**
|
||||
If neither A nor B is feasible right now, commit **two** placeholder images with full honest provenance saying "placeholder" — enough to prove the schema accepts real entries — and **record the IOU in a dedicated file** at `.planning/phases/01-foundations-and-doctrine/01-05-IOU.md` (do **not** edit `.planning/STATE.md` from a phase-internal task — STATE.md is owned by the orchestrator, per WARNING 5 fix). The IOU file contains date, owner, and the deferred-work statement; the verification phase / Phase-5 entry will surface this IOU back into STATE.md if it is still open at that point. This still satisfies CONTEXT D-01's "10–20 hand-curated" loosely (with explicit IOU) and keeps the rest of Phase 1 unblocked.
|
||||
|
||||
Whichever path you choose, also write `assets/north-stars/README.md` documenting:
|
||||
- What this directory is (the visual ground truth for Phase 5+ regression)
|
||||
- Which path was chosen (A/B/C) and why
|
||||
- How to add new images (sidecar naming convention, the 6 required fields)
|
||||
- When this set will be revisited (Phase 5 is the planned consolidation point per CONTEXT D-02)
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Choose a path (A, B, or C) and produce the images + sidecars.
|
||||
2. Place all pairs under `assets/north-stars/`. Naming: `<slug>.png` + `<slug>.png.provenance.json` (per RESEARCH Pattern 6 sidecar naming convention).
|
||||
3. Write `assets/north-stars/README.md` (~10 lines, see template above).
|
||||
4. Run `node scripts/validate-assets.mjs` — must exit 0 with `[provenance] all <N> assets carry valid provenance.` where N matches the count of images you committed.
|
||||
5. Run `npm test` — must remain green; the validate-assets test should now also count the new assets.
|
||||
6. Run `npm run ci` — must exit 0 (lint + test + validate:assets + build).
|
||||
7. Commit with message `feat(01-05): commit <N> north-star reference images with provenance sidecars (path <A|B|C>)`.
|
||||
8. **If you chose Path C, also create `.planning/phases/01-foundations-and-doctrine/01-05-IOU.md`** with this content (do NOT edit `.planning/STATE.md` directly — that file is orchestrator-owned, per WARNING 5 fix):
|
||||
```markdown
|
||||
# Plan 05 IOU — North-Star Reference Set Expansion
|
||||
|
||||
**Date:** <YYYY-MM-DD>
|
||||
**Owner:** <your name or handle>
|
||||
**Phase:** 01 (Foundations & Doctrine)
|
||||
**Plan:** 05 (Asset Provenance)
|
||||
|
||||
## Deferred Work
|
||||
|
||||
Path C was selected for Plan 05 Task 2: the north-star reference set under
|
||||
`assets/north-stars/` currently contains <N> placeholder images (target: 10–20).
|
||||
Expansion to the full curated set must happen before Phase 5 begins, since
|
||||
Phase 5+ visual regression depends on this seed.
|
||||
|
||||
## Trigger
|
||||
|
||||
Phase 5 entry; or sooner if an AI image tool / hand-painted batch becomes available.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- 10–20 final images committed under `assets/north-stars/` with valid provenance sidecars.
|
||||
- This file deleted on resolution (or marked `RESOLVED` with the resolving commit hash).
|
||||
```
|
||||
|
||||
Type `approved` when done, or describe issues encountered (e.g., "AI tool unavailable, going with Path B / Path C").
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" or describe issues</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-01-06 | Spoofing | Provenance sidecar fabrication (a contributor adds an asset with fabricated provenance) | accept (out of scope for Phase 1) | Single-developer project in Phase 1; not a real threat. RESEARCH § Security Domain explicitly defers this to Phase 8+ when external contributors enter the picture, with `human_reviewed_by` field signed by a curator. |
|
||||
| T-01-07 | Tampering | Path traversal via sidecar filename | accept | Sidecars are walked by the validator using `node:fs/promises readdir` and reads are confined to paths under `/assets/` (or `process.env.ASSETS_DIR`). The validator never resolves paths from sidecar contents. Not exploitable. |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `node scripts/validate-assets.mjs` exits 0 against the real `/assets/` tree (north-star set + refused-sample dir).
|
||||
- `npx vitest run scripts/validate-assets.test.ts` passes (the gate is structurally enforced — an asset missing provenance under an isolated `os.tmpdir()` fixture fails the script).
|
||||
- `assets/__samples__/refused/no-provenance.png` exists and has no sidecar (proof-of-gate artifact).
|
||||
- `assets/north-stars/` contains the curated reference set per Path A/B/C with valid provenance sidecars (or the explicit IOU file at `.planning/phases/01-foundations-and-doctrine/01-05-IOU.md` per Path C).
|
||||
- `npm run ci` exits 0 — Plan 07's CI workflow will run this.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 30-line standalone Node script validates every non-sidecar asset has a sibling `<filename>.provenance.json` per the 6-field schema.
|
||||
- Refused-sample fixture proves the gate by being intentionally excluded.
|
||||
- Vitest integration test creates an `os.tmpdir()`-isolated missing-provenance fixture, asserts non-zero exit, cleans up — no real-tree pollution risk.
|
||||
- 10–20 north-star reference images committed with valid sidecars (Path A / B / C per CONTEXT D-01 + RESEARCH Open Question #5).
|
||||
- `npm run validate:assets` (the package.json script Plan 01 created) exits 0.
|
||||
- Phase 5 has a working seed for visual regression and asset-pipeline scale-up.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-foundations-and-doctrine/01-05-SUMMARY.md` documenting:
|
||||
- The chosen path (A / B / C) and why.
|
||||
- The exact count of north-star images committed (the validator output's `all <N> assets` count).
|
||||
- The `model_id` values present in the provenance set (so Phase 5 sees what tools were used in Phase 1).
|
||||
- Confirmation that `npm run validate:assets` exits 0 and `npx vitest run scripts/validate-assets.test.ts` is green.
|
||||
- Note for Phase 5: the schema is `provenance_schema_version: 1` (implicit / unset); Phase 5 may bump this when vendor consolidation lands.
|
||||
- If Path C: explicit IOU recorded at `.planning/phases/01-foundations-and-doctrine/01-05-IOU.md` (NOT in STATE.md — STATE.md is orchestrator-owned).
|
||||
</output>
|
||||
</output>
|
||||
Reference in New Issue
Block a user