@antoneeo/course-creator 0.1.0-beta.16 → 0.1.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-beta.17] - 2026-10-31
4
+
5
+ ### What's new
6
+
7
+ **Decisions taken on your behalf are labelled.** In the request record, a decision a session took under a delegation you gave it, and words you gave one session for work another session records, now carry their own heading. They are never read later as something you said yourself, and a delegated decision never overrides one of yours: a choice that would change it comes back to you as a question.
8
+
9
+ #### What to do
10
+
11
+ **1. Update (everyone).** The changes arrive only with the update.
12
+ 1. Run `npm install -g @antoneeo/course-creator@latest`, then `course-creator-install-skill`.
13
+ 2. Close and reopen your agent sessions: the new version loads only at startup.
14
+
15
+ To check: `npm ls -g @antoneeo/course-creator` shows 0.1.0-beta.17.
16
+
17
+ ### Changed
18
+ - Request record (`templates.md`, F-088): labelled entries `decided under delegation R<m>` and `received for <unit>`; `review.md` and `elicitation.md` treat them as the deciding session's choice or relayed words, never an owner statement.
19
+
3
20
  ## [0.1.0-beta.16] - 2026-10-31
4
21
 
5
22
  ### What's new
package/README.md CHANGED
@@ -6,9 +6,9 @@ The package is the fourth lens of the Agentic SDLC family. It uses the shared `a
6
6
 
7
7
  <!-- whats-new:begin -->
8
8
  <!-- generated by scripts/whats_new.js from distributions/course-creator/CHANGELOG.md: edit the CHANGELOG, then run node scripts/whats_new.js --write -->
9
- ## What's new in 0.1.0-beta.16
9
+ ## What's new in 0.1.0-beta.17
10
10
 
11
- **You approve the course design before production.** On significant work, such as a new course or a changed audience, once the independent design review passes, the agent stops and shows you a short summary of the design: what the course will teach and to whom, what it touches, the risks and how they are handled, and what the review found. Production starts only after your yes, which the agent records in the unit's request record. A "go ahead" given before you saw the design does not count, and in a session with nobody to answer the unit waits for you.
11
+ **Decisions taken on your behalf are labelled.** In the request record, a decision a session took under a delegation you gave it, and words you gave one session for work another session records, now carry their own heading. They are never read later as something you said yourself, and a delegated decision never overrides one of yours: a choice that would change it comes back to you as a question.
12
12
 
13
13
  #### What to do
14
14
 
@@ -16,12 +16,7 @@ The package is the fourth lens of the Agentic SDLC family. It uses the shared `a
16
16
  1. Run `npm install -g @antoneeo/course-creator@latest`, then `course-creator-install-skill`.
17
17
  2. Close and reopen your agent sessions: the new version loads only at startup.
18
18
 
19
- To check: `npm ls -g @antoneeo/course-creator` shows 0.1.0-beta.16.
20
-
21
- **2. Approving the design (significant units).**
22
- - **This concerns you if** you use the skill for significant (L3) work. Nothing to configure: after the design review passes, the agent asks whether to proceed. Answer yes to start the production, or say what to change; the design is then revised and, where the change touches what the review judged, reviewed again before you are asked again.
23
- - **A unit already in progress.** Units started before 2026-10-31 are not affected. If a unit started on or after 2026-10-31 with an earlier version is already in progress or finished, `sdlc_check.py validate` shows a `[note]` saying it has `no design approval in its request record`. Why: the earlier version never asked you. To clear it, tell the agent that you approve the design as it was implemented; it records your words under a label that begins `design approved` (for example `design approved after implementation`). The note never fails a check, so you can also leave it.
24
- - **To check:** on your next significant unit, the agent stops after the design review and asks you before any production starts.
19
+ To check: `npm ls -g @antoneeo/course-creator` shows 0.1.0-beta.17.
25
20
 
26
21
  Full history: [CHANGELOG](https://github.com/Antoneeo/agentic-sdlc-skill/blob/main/distributions/course-creator/CHANGELOG.md)
27
22
  <!-- whats-new:end -->
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "course-creator",
3
- "version": "0.1.0-beta.16",
3
+ "version": "0.1.0-beta.17",
4
4
  "description": "Course creation for defined learners, with progressive explanations, sourced facts and diagnostic independent testing.",
5
5
  "author": "Antonio Pinto (https://github.com/Antoneeo)"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antoneeo/course-creator",
3
- "version": "0.1.0-beta.16",
3
+ "version": "0.1.0-beta.17",
4
4
  "description": "Design sourced, progressive courses for a defined learner with Agentic SDLC governance and diagnostic learner simulations.",
5
5
  "keywords": ["claude-code", "gemini-cli", "codex", "course-design", "skill", "ai-agent"],
6
6
  "author": "Antonio Pinto (https://github.com/Antoneeo)",
package/scripts/init.js CHANGED
@@ -1,62 +1,62 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { execFileSync } = require('child_process');
6
- const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES,
7
- CLIENTS, clientDetected, skillTarget, wireOrientHook, detectPython } = require('./lib');
8
-
9
- const root = process.cwd();
10
- const seed = new Map([
11
- ['ai_docs/README.md', `---\ndefault_domain: course\n---\n# Project knowledge and course work\n\nRead vision/project_vision.md, INDEX.md and reference/INDEX.md first.\n`],
12
- ['ai_docs/vision/project_vision.md', `---\ndescription: Project benefit and boundaries.\nstatus: DRAFT\n---\n# Project Vision\nStatus: DRAFT\n<!-- STUB: awaiting the owner interview (vision.md §The owner interview). Not a\n Vision: no artifact traces to it. -->\n`],
13
- ['ai_docs/vision/roadmap.md', `---\ndescription: Project learning and delivery roadmap.\nstatus: DRAFT\n---\n# Roadmap\nStatus: DRAFT\n`],
14
- ['ai_docs/vision/principles.md', `---\ndescription: Decisions that constrain project work.\nstatus: DRAFT\n---\n# Principles\nStatus: DRAFT\n`],
15
- ['ai_docs/strategic/architecture.md', `---\ndescription: Verified component inventory.\nstatus: CURRENT\n---\n# Architecture\n\n## Component Map\n| Component | Capability it owns | Contract | Where |\n|---|---|---|---|\n\n## Architectural Patterns\n<Add only verified patterns.>\n`],
16
- ['ai_docs/strategic/existing_features.md', `---\ndescription: Existing capabilities verified in this project.\nstatus: CURRENT\n---\n# Existing Features\n`],
17
- ['ai_docs/audit/audit_plan.md', `# Audit Plan\n\n| Area | State | Reference | Notes |\n|---|---|---|---|\n| . | PENDING | - | Initial analysis |\n`],
18
- ]);
19
- function writeIfAbsent(rel, content) {
20
- const dest = path.join(root, rel);
21
- if (fs.existsSync(dest)) return false;
22
- fs.mkdirSync(path.dirname(dest), { recursive: true });
23
- fs.writeFileSync(dest, content, 'utf8');
24
- console.log(`Created ${rel}`);
25
- return true;
26
- }
27
- for (const [rel, content] of seed) writeIfAbsent(rel, content);
28
- for (const dir of ['ai_docs/vision/features', 'ai_docs/reference', 'ai_docs/solutions/courses',
29
- 'ai_docs/audit/reviews']) fs.mkdirSync(path.join(root, dir), { recursive: true });
30
-
31
- const pointer = `# Course Creator project protocol\n\nUse the installed \`course-creator\` skill when the deliverable teaches a defined learner. It applies Agentic SDLC in Standalone mode: triage, project and course Vision, ANALYSIS, progressive explanations, sourced facts, independent diagnostic testing and a truthful efficacy label. Course files live in \`ai_docs/solutions/courses/<slug>/\`. Read \`ai_docs/README.md\` and the skill before working.\n\nInstall if missing: \`npm i -g @antoneeo/course-creator@beta && course-creator-install-skill\`.\n`;
32
- const pointers = { claude: 'CLAUDE.md', gemini: 'GEMINI.md', codex: 'AGENTS.md',
33
- antigravity: 'AGENTS.md' };
34
- const detected = CLIENTS.filter(clientDetected);
35
- for (const client of detected) writeIfAbsent(pointers[client.key], pointer);
36
- writeIfAbsent('.cursorrules', pointer);
37
- const siblings = new Map();
38
- for (const client of detected) {
39
- const parent = path.dirname(skillTarget(client));
40
- for (const [name, lens] of Object.entries(SIBLING_LENSES)) {
41
- if (fs.existsSync(path.join(parent, name))) siblings.set(name, lens);
42
- }
43
- }
44
- if (siblings.size) {
45
- const rows = [`- \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens`,
46
- ...[...siblings].map(([name, lens]) => `- \`${name}\` — the **${lens}** lens`)];
47
- writeIfAbsent('AGENTIC_MULTI_LENS.md',
48
- `# Agentic SDLC family routing\n\nRead \`routing.md\` to choose the owner of each deliverable; \`memory.md\` is shared.\n\n${rows.join('\n')}\n`);
49
- }
50
- const python = detectPython();
51
- for (const client of detected.filter(c => c.key === 'claude')) {
52
- const result = wireOrientHook({ cwd: root, client, python, docsLabel: 'ai_docs' });
53
- console.log(`Claude SessionStart orientation: ${result.code}`);
54
- }
55
- if (python) {
56
- try {
57
- execFileSync(python, [path.join(SKILL_SOURCE, 'scripts/sdlc_check.py'),
58
- 'index', '--root', root], { stdio: 'ignore' });
59
- console.log('Generated ai_docs indexes.');
60
- } catch (e) { console.log('Run sdlc_check.py index when Python is available.'); }
61
- }
62
- console.log('Course Creator initialization complete.');
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execFileSync } = require('child_process');
6
+ const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES,
7
+ CLIENTS, clientDetected, skillTarget, wireOrientHook, detectPython } = require('./lib');
8
+
9
+ const root = process.cwd();
10
+ const seed = new Map([
11
+ ['ai_docs/README.md', `---\ndefault_domain: course\n---\n# Project knowledge and course work\n\nRead vision/project_vision.md, INDEX.md and reference/INDEX.md first.\n`],
12
+ ['ai_docs/vision/project_vision.md', `---\ndescription: Project benefit and boundaries.\nstatus: DRAFT\n---\n# Project Vision\nStatus: DRAFT\n<!-- STUB: awaiting the owner interview (vision.md §The owner interview). Not a\n Vision: no artifact traces to it. -->\n`],
13
+ ['ai_docs/vision/roadmap.md', `---\ndescription: Project learning and delivery roadmap.\nstatus: DRAFT\n---\n# Roadmap\nStatus: DRAFT\n`],
14
+ ['ai_docs/vision/principles.md', `---\ndescription: Decisions that constrain project work.\nstatus: DRAFT\n---\n# Principles\nStatus: DRAFT\n`],
15
+ ['ai_docs/strategic/architecture.md', `---\ndescription: Verified component inventory.\nstatus: CURRENT\n---\n# Architecture\n\n## Component Map\n| Component | Capability it owns | Contract | Where |\n|---|---|---|---|\n\n## Architectural Patterns\n<Add only verified patterns.>\n`],
16
+ ['ai_docs/strategic/existing_features.md', `---\ndescription: Existing capabilities verified in this project.\nstatus: CURRENT\n---\n# Existing Features\n`],
17
+ ['ai_docs/audit/audit_plan.md', `# Audit Plan\n\n| Area | State | Reference | Notes |\n|---|---|---|---|\n| . | PENDING | - | Initial analysis |\n`],
18
+ ]);
19
+ function writeIfAbsent(rel, content) {
20
+ const dest = path.join(root, rel);
21
+ if (fs.existsSync(dest)) return false;
22
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
23
+ fs.writeFileSync(dest, content, 'utf8');
24
+ console.log(`Created ${rel}`);
25
+ return true;
26
+ }
27
+ for (const [rel, content] of seed) writeIfAbsent(rel, content);
28
+ for (const dir of ['ai_docs/vision/features', 'ai_docs/reference', 'ai_docs/solutions/courses',
29
+ 'ai_docs/audit/reviews']) fs.mkdirSync(path.join(root, dir), { recursive: true });
30
+
31
+ const pointer = `# Course Creator project protocol\n\nUse the installed \`course-creator\` skill when the deliverable teaches a defined learner. It applies Agentic SDLC in Standalone mode: triage, project and course Vision, ANALYSIS, progressive explanations, sourced facts, independent diagnostic testing and a truthful efficacy label. Course files live in \`ai_docs/solutions/courses/<slug>/\`. Read \`ai_docs/README.md\` and the skill before working.\n\nInstall if missing: \`npm i -g @antoneeo/course-creator@beta && course-creator-install-skill\`.\n`;
32
+ const pointers = { claude: 'CLAUDE.md', gemini: 'GEMINI.md', codex: 'AGENTS.md',
33
+ antigravity: 'AGENTS.md' };
34
+ const detected = CLIENTS.filter(clientDetected);
35
+ for (const client of detected) writeIfAbsent(pointers[client.key], pointer);
36
+ writeIfAbsent('.cursorrules', pointer);
37
+ const siblings = new Map();
38
+ for (const client of detected) {
39
+ const parent = path.dirname(skillTarget(client));
40
+ for (const [name, lens] of Object.entries(SIBLING_LENSES)) {
41
+ if (fs.existsSync(path.join(parent, name))) siblings.set(name, lens);
42
+ }
43
+ }
44
+ if (siblings.size) {
45
+ const rows = [`- \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens`,
46
+ ...[...siblings].map(([name, lens]) => `- \`${name}\` — the **${lens}** lens`)];
47
+ writeIfAbsent('AGENTIC_MULTI_LENS.md',
48
+ `# Agentic SDLC family routing\n\nRead \`routing.md\` to choose the owner of each deliverable; \`memory.md\` is shared.\n\n${rows.join('\n')}\n`);
49
+ }
50
+ const python = detectPython();
51
+ for (const client of detected.filter(c => c.key === 'claude')) {
52
+ const result = wireOrientHook({ cwd: root, client, python, docsLabel: 'ai_docs' });
53
+ console.log(`Claude SessionStart orientation: ${result.code}`);
54
+ }
55
+ if (python) {
56
+ try {
57
+ execFileSync(python, [path.join(SKILL_SOURCE, 'scripts/sdlc_check.py'),
58
+ 'index', '--root', root], { stdio: 'ignore' });
59
+ console.log('Generated ai_docs indexes.');
60
+ } catch (e) { console.log('Run sdlc_check.py index when Python is available.'); }
61
+ }
62
+ console.log('Course Creator initialization complete.');
package/scripts/lib.js CHANGED
@@ -3,8 +3,8 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const os = require('os');
7
- const crypto = require('crypto');
6
+ const os = require('os');
7
+ const crypto = require('crypto');
8
8
  const { execSync } = require('child_process');
9
9
 
10
10
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
@@ -449,104 +449,104 @@ function pathsEqual(a, b) {
449
449
  return false;
450
450
  }
451
451
  }
452
- function pathInside(child, parent) {
452
+ function pathInside(child, parent) {
453
453
  try {
454
454
  const rel = path.relative(String(parent), String(child));
455
455
  return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
456
456
  } catch (e) {
457
457
  return false;
458
458
  }
459
- }
460
-
461
- // Ownership is per file. An existing directory without our marker belongs to its
462
- // owner, and a file edited after installation is retained on uninstall.
463
- const INSTALL_MARKER = '.course-creator-install.json';
464
- function hashFile(file) {
465
- return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
466
- }
467
- function sourceFiles(dir = SKILL_SOURCE, prefix = '') {
468
- const found = [];
469
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
470
- if (entry.isSymbolicLink()) continue;
471
- const rel = path.join(prefix, entry.name);
472
- if (entry.isDirectory()) found.push(...sourceFiles(path.join(dir, entry.name), rel));
473
- else if (entry.isFile()) found.push(rel);
474
- }
475
- return found;
476
- }
477
- function safeOwnedPath(target, rel) {
478
- if (path.isAbsolute(rel) || rel.split(/[\\/]/).includes('..')) return null;
479
- const full = path.resolve(target, rel);
480
- if (!pathInside(full, target)) return null;
481
- let cur = path.resolve(target);
482
- for (const part of path.relative(cur, full).split(path.sep)) {
483
- cur = path.join(cur, part);
484
- if (fs.existsSync(cur) && fs.lstatSync(cur).isSymbolicLink()) return null;
485
- }
486
- return full;
487
- }
488
- function ownedMarker(target) {
489
- const marker = path.join(target, INSTALL_MARKER);
490
- if (!fs.existsSync(marker)) return null;
491
- try {
492
- if (!fs.lstatSync(marker).isFile() || fs.lstatSync(marker).isSymbolicLink()) return null;
493
- const data = JSON.parse(fs.readFileSync(marker, 'utf8'));
494
- return data.skill === INSTALLED_SKILL_NAME && data.files &&
495
- typeof data.files === 'object' ? data : null;
496
- } catch (e) { return null; }
497
- }
498
- function installOwned(target) {
499
- if (!fs.existsSync(SKILL_SOURCE)) return { code: 'missing-source' };
500
- if (fs.existsSync(target) && (!fs.lstatSync(target).isDirectory() ||
501
- fs.lstatSync(target).isSymbolicLink())) return { code: 'unowned' };
502
- const existing = fs.existsSync(target) ? fs.readdirSync(target) : [];
503
- const prior = ownedMarker(target);
504
- if (existing.length && !prior) return { code: 'unowned' };
505
- fs.mkdirSync(target, { recursive: true });
506
- const owned = { ...(prior ? prior.files : {}) };
507
- let copied = 0, preserved = 0;
508
- for (const rel of sourceFiles()) {
509
- const dest = safeOwnedPath(target, rel);
510
- if (!dest) { preserved++; continue; }
511
- if (fs.existsSync(dest)) {
512
- if (!Object.hasOwn(owned, rel) || hashFile(dest) !== owned[rel]) {
513
- preserved++; continue;
514
- }
515
- }
516
- fs.mkdirSync(path.dirname(dest), { recursive: true });
517
- fs.copyFileSync(path.join(SKILL_SOURCE, rel), dest);
518
- owned[rel] = hashFile(dest);
519
- copied++;
520
- }
521
- fs.writeFileSync(path.join(target, INSTALL_MARKER),
522
- JSON.stringify({ skill: INSTALLED_SKILL_NAME, files: owned }, null, 2) + '\n');
523
- return { code: 'installed', copied, preserved };
524
- }
525
- function uninstallOwned(target) {
526
- if (!fs.existsSync(target) || fs.lstatSync(target).isSymbolicLink()) {
527
- return { code: 'unowned' };
528
- }
529
- const prior = ownedMarker(target);
530
- if (!prior) return { code: 'unowned' };
531
- let removed = 0, preserved = 0;
532
- for (const [rel, hash] of Object.entries(prior.files)) {
533
- const dest = safeOwnedPath(target, rel);
534
- if (!dest || !fs.existsSync(dest) || !fs.lstatSync(dest).isFile() ||
535
- hashFile(dest) !== hash) { preserved++; continue; }
536
- fs.unlinkSync(dest);
537
- removed++;
538
- }
539
- fs.unlinkSync(path.join(target, INSTALL_MARKER));
540
- // Remove only empty directories; a user-owned file keeps its parent.
541
- const dirs = new Set(Object.keys(prior.files).map(rel => path.dirname(rel)));
542
- for (const rel of [...dirs].sort((a, b) => b.length - a.length)) {
543
- const dir = path.resolve(target, rel);
544
- if (!pathInside(dir, target)) continue;
545
- try { fs.rmdirSync(dir); } catch (e) { /* non-empty or absent */ }
546
- }
547
- try { fs.rmdirSync(target); } catch (e) { /* user files remain */ }
548
- return { code: 'removed', removed, preserved };
549
- }
459
+ }
460
+
461
+ // Ownership is per file. An existing directory without our marker belongs to its
462
+ // owner, and a file edited after installation is retained on uninstall.
463
+ const INSTALL_MARKER = '.course-creator-install.json';
464
+ function hashFile(file) {
465
+ return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
466
+ }
467
+ function sourceFiles(dir = SKILL_SOURCE, prefix = '') {
468
+ const found = [];
469
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
470
+ if (entry.isSymbolicLink()) continue;
471
+ const rel = path.join(prefix, entry.name);
472
+ if (entry.isDirectory()) found.push(...sourceFiles(path.join(dir, entry.name), rel));
473
+ else if (entry.isFile()) found.push(rel);
474
+ }
475
+ return found;
476
+ }
477
+ function safeOwnedPath(target, rel) {
478
+ if (path.isAbsolute(rel) || rel.split(/[\\/]/).includes('..')) return null;
479
+ const full = path.resolve(target, rel);
480
+ if (!pathInside(full, target)) return null;
481
+ let cur = path.resolve(target);
482
+ for (const part of path.relative(cur, full).split(path.sep)) {
483
+ cur = path.join(cur, part);
484
+ if (fs.existsSync(cur) && fs.lstatSync(cur).isSymbolicLink()) return null;
485
+ }
486
+ return full;
487
+ }
488
+ function ownedMarker(target) {
489
+ const marker = path.join(target, INSTALL_MARKER);
490
+ if (!fs.existsSync(marker)) return null;
491
+ try {
492
+ if (!fs.lstatSync(marker).isFile() || fs.lstatSync(marker).isSymbolicLink()) return null;
493
+ const data = JSON.parse(fs.readFileSync(marker, 'utf8'));
494
+ return data.skill === INSTALLED_SKILL_NAME && data.files &&
495
+ typeof data.files === 'object' ? data : null;
496
+ } catch (e) { return null; }
497
+ }
498
+ function installOwned(target) {
499
+ if (!fs.existsSync(SKILL_SOURCE)) return { code: 'missing-source' };
500
+ if (fs.existsSync(target) && (!fs.lstatSync(target).isDirectory() ||
501
+ fs.lstatSync(target).isSymbolicLink())) return { code: 'unowned' };
502
+ const existing = fs.existsSync(target) ? fs.readdirSync(target) : [];
503
+ const prior = ownedMarker(target);
504
+ if (existing.length && !prior) return { code: 'unowned' };
505
+ fs.mkdirSync(target, { recursive: true });
506
+ const owned = { ...(prior ? prior.files : {}) };
507
+ let copied = 0, preserved = 0;
508
+ for (const rel of sourceFiles()) {
509
+ const dest = safeOwnedPath(target, rel);
510
+ if (!dest) { preserved++; continue; }
511
+ if (fs.existsSync(dest)) {
512
+ if (!Object.hasOwn(owned, rel) || hashFile(dest) !== owned[rel]) {
513
+ preserved++; continue;
514
+ }
515
+ }
516
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
517
+ fs.copyFileSync(path.join(SKILL_SOURCE, rel), dest);
518
+ owned[rel] = hashFile(dest);
519
+ copied++;
520
+ }
521
+ fs.writeFileSync(path.join(target, INSTALL_MARKER),
522
+ JSON.stringify({ skill: INSTALLED_SKILL_NAME, files: owned }, null, 2) + '\n');
523
+ return { code: 'installed', copied, preserved };
524
+ }
525
+ function uninstallOwned(target) {
526
+ if (!fs.existsSync(target) || fs.lstatSync(target).isSymbolicLink()) {
527
+ return { code: 'unowned' };
528
+ }
529
+ const prior = ownedMarker(target);
530
+ if (!prior) return { code: 'unowned' };
531
+ let removed = 0, preserved = 0;
532
+ for (const [rel, hash] of Object.entries(prior.files)) {
533
+ const dest = safeOwnedPath(target, rel);
534
+ if (!dest || !fs.existsSync(dest) || !fs.lstatSync(dest).isFile() ||
535
+ hashFile(dest) !== hash) { preserved++; continue; }
536
+ fs.unlinkSync(dest);
537
+ removed++;
538
+ }
539
+ fs.unlinkSync(path.join(target, INSTALL_MARKER));
540
+ // Remove only empty directories; a user-owned file keeps its parent.
541
+ const dirs = new Set(Object.keys(prior.files).map(rel => path.dirname(rel)));
542
+ for (const rel of [...dirs].sort((a, b) => b.length - a.length)) {
543
+ const dir = path.resolve(target, rel);
544
+ if (!pathInside(dir, target)) continue;
545
+ try { fs.rmdirSync(dir); } catch (e) { /* non-empty or absent */ }
546
+ }
547
+ try { fs.rmdirSync(target); } catch (e) { /* user files remain */ }
548
+ return { code: 'removed', removed, preserved };
549
+ }
550
550
 
551
551
  // The marker lives under the family's agent-global root (the same root the
552
552
  // validator's AGENTIC_SDLC_KB_ROOT seam names), read at call time so the test
@@ -720,10 +720,10 @@ module.exports = {
720
720
  detectPython,
721
721
  pathsEqual,
722
722
  orientHookCommand,
723
- copyRecursive,
724
- installOwned,
725
- uninstallOwned,
726
- ownedMarker,
723
+ copyRecursive,
724
+ installOwned,
725
+ uninstallOwned,
726
+ ownedMarker,
727
727
  loadTemplates,
728
728
  templateFor,
729
729
  };
@@ -1,16 +1,16 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- const { CLIENTS, clientDetected, skillTarget, installOwned,
4
- wireGlobalOrientHook, detectPython } = require('./lib');
5
-
6
- for (const client of CLIENTS) {
7
- if (!clientDetected(client)) continue;
8
- const target = skillTarget(client);
9
- const result = installOwned(target);
10
- console.log(`${client.label}: ${result.code} (${target})`);
11
- if (client.key === 'claude' && result.code === 'installed') {
12
- try { wireGlobalOrientHook({ client, python: detectPython() }); }
13
- catch (e) { console.log('Session orientation can be wired from ENFORCEMENT.md.'); }
14
- }
15
- }
16
- console.log('Initialize a project with course-creator-init.');
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const { CLIENTS, clientDetected, skillTarget, installOwned,
4
+ wireGlobalOrientHook, detectPython } = require('./lib');
5
+
6
+ for (const client of CLIENTS) {
7
+ if (!clientDetected(client)) continue;
8
+ const target = skillTarget(client);
9
+ const result = installOwned(target);
10
+ console.log(`${client.label}: ${result.code} (${target})`);
11
+ if (client.key === 'claude' && result.code === 'installed') {
12
+ try { wireGlobalOrientHook({ client, python: detectPython() }); }
13
+ catch (e) { console.log('Session orientation can be wired from ENFORCEMENT.md.'); }
14
+ }
15
+ }
16
+ console.log('Initialize a project with course-creator-init.');
@@ -1,12 +1,12 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- const { CLIENTS, skillTarget, ownedMarker, uninstallOwned,
4
- removeGlobalOrientHooks } = require('./lib');
5
-
6
- const ownedTargets = CLIENTS.map(skillTarget).filter(ownedMarker);
7
- try { removeGlobalOrientHooks(ownedTargets); } catch (e) { /* convenience only */ }
8
- for (const target of ownedTargets) {
9
- const result = uninstallOwned(target);
10
- console.log(`${result.code}: ${target} (${result.removed} owned files removed; ` +
11
- `${result.preserved} retained)`);
12
- }
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const { CLIENTS, skillTarget, ownedMarker, uninstallOwned,
4
+ removeGlobalOrientHooks } = require('./lib');
5
+
6
+ const ownedTargets = CLIENTS.map(skillTarget).filter(ownedMarker);
7
+ try { removeGlobalOrientHooks(ownedTargets); } catch (e) { /* convenience only */ }
8
+ for (const target of ownedTargets) {
9
+ const result = uninstallOwned(target);
10
+ console.log(`${result.code}: ${target} (${result.removed} owned files removed; ` +
11
+ `${result.preserved} retained)`);
12
+ }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: course-creator
3
- version: 0.1.0-beta.16
3
+ version: 0.1.0-beta.17
4
4
  description: Design and improve courses that teach a defined learner through sourced, progressive explanations. Use when the deliverable is instruction or a learning path; route corpus intake to kb-agentic and persuasive campaigns to marketing-agentic.
5
5
  author: Antonio Pinto (https://github.com/Antoneeo)
6
6
  copyright: (c) 2026 Antonio Pinto
@@ -89,7 +89,9 @@ intent choice with neither a source nor a weighing is a finding (`review.md`
89
89
  - **Unattended** — the practitioner is not reachable (the Unattended path above).
90
90
  - **Delegated** — their own words handed you this choice; cite their entry in the
91
91
  request record, or quote them where the unit has none (below L3). A general
92
- "go ahead" is not a delegation.
92
+ "go ahead" is not a delegation. A choice you make under it goes, where the
93
+ unit has a request record, into it as a delegated entry (`templates.md` §REQUEST) and the artifact
94
+ cites that entry; it never changes an earlier decision of theirs — ask instead.
93
95
 
94
96
  Each states **what it is taken from** and **the alternative it excludes**, and is
95
97
  listed where the practitioner will read it — an assumption nobody is shown is a
@@ -467,7 +467,9 @@ When you are the reviewer:
467
467
  the artifact relies on — an answer, an approval, a cost acceptance, a ruling, a
468
468
  delegation — that resolves to no entry of the unit's request record, or that the
469
469
  record contradicts, is a finding; so is a quotation in the record carrying what
470
- its template says to redact. What makes a ground
470
+ its template says to redact, a delegated entry citing no delegation or changing
471
+ an earlier owner decision, and an artifact of a shared unit citing a received
472
+ entry instead of the shared record (`templates.md` §REQUEST, labelled entries). What makes a ground
471
473
  or an assumption legal is each lens's `elicitation.md` §The question discipline —
472
474
  cite it, never restate it. The fix is the question, not a better justification.
473
475
  - **Restated facts (cite, never copy).** Every governance slot has ONE owning document
@@ -1,38 +1,38 @@
1
- {
2
- "_comment": "Generated by shared_files.py --update. IDENTICAL in every distribution of the family: if two copies of this file differ, the spine has diverged. Never edit by hand.",
3
- "files": {
4
- "LICENSE": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
5
- "NOTICE": "8414f424f7c1db19d89d74286fd2d6b1acdd9eedde910fa57a16d6835602e4bd",
6
- "dispatch.md": "d127057198415f81b921c74e3234a47d5794a2af662eeadaf3dff3cc1739ebc8",
7
- "docs_site/app.js": "ce9ed4341f7d24b648dee7531700d09f4af07f7e9ffc4be90aff29ecc276f27f",
8
- "docs_site/index.html": "2b64167470f754e46fa4bb4e73a3d2c34ee632d91fe2f17705a58d18b5018af5",
9
- "docs_site/strings.js": "440c11830f87602152422766b6d5b84da904150c37b0b6347e2ad53a7fec62b8",
10
- "docs_site/style.css": "1cbd71eb2e62bb9251ecb62796fe4cecf6a78eecf2f9a7493703290ae55d1244",
11
- "guides.md": "8fd85ab176d01ad7fc4c72020afa062f37f3c2f6b380f064c20f114c0680b46b",
12
- "memory.md": "765c8cf34e9161143d6f8b982106251de0ac7f4f6f5d768336f3e4b4b150823e",
13
- "review.md": "de1a2e083dc284c54283c9e55d1c3a1500f6f152a8948ca475664712a81577af",
14
- "routing.md": "5116e8dbd3b215919b897506f28917ac8f1c44cd91f8b64ec901048320e6c1f6",
15
- "scripts/docs_site.py": "bfc7a521142251b85b229e1873ccf2e375bb5c7f1990cef574e1a78ef14f0091",
16
- "scripts/entry_point.py": "4a560d4d41db989d3426182f1b24270cbb934b3db0f3246c700578a4ed93089b",
17
- "scripts/knowledge.py": "6047deb7a2be4cf9ef2949cbcf8a52a8ea61500f4e12d203f1c58076540d50ab",
18
- "scripts/sdlc_core.py": "582eb73e1b9989bb952c72721bd80b5bd76447955bca85bfd789ea3e94a77afc",
19
- "scripts/shared_files.py": "d360c6018c602788dd96194bcc0f8e8409b140671857ae50ec15acee882eb51e",
20
- "scripts/test_audit_refs.py": "5c207be46da566db47069b08373195e748fca19c047418f09cc2f78841dfd9a5",
21
- "scripts/test_chronology.py": "ef482ed2261822778c69ade5dae6c875eca97ae8ef4f47b60bc262ac857eb95c",
22
- "scripts/test_constraints.py": "4be3bbff7032e53b464de2f4e2eb18a824311adb2359c77fb9be065aded09239",
23
- "scripts/test_design_approval.py": "191856214cb0b4ab8606b57496cf54742568e3bb4bfa0d8f0c64889f2e2d1e63",
24
- "scripts/test_docs_root.py": "19753e0316c0df63ed321d24b411a9ff36eea2058421fd69fc57e507d75566d0",
25
- "scripts/test_docs_site.py": "391ec5c635c67f0fbefe458426e352b5b1ef626ae65c099b1e8e1db491d0a5f9",
26
- "scripts/test_domain_rules.py": "33542b5dc2bb77c05ece55cf0446065cc4226c763ef5e040aa77364f226937bb",
27
- "scripts/test_drift.py": "c7b7121a8a308b0f3fe12c0c0c346444e632b7aaeff47dc8818b621e5c20cb38",
28
- "scripts/test_guide_check.py": "19c7a6f009fa1b7f70b0beba059b97fa05e69255f6b2dec8829a87bd9fba283f",
29
- "scripts/test_merge_safety.py": "8b9241077bf7ecd6dd335907c640044ab38968ff4c9fdc464450e732cb77adf4",
30
- "scripts/test_migrate.py": "c129823bf4feb0614acd975351632a6ae62d7896a0ef32724419a8e9335e3110",
31
- "scripts/test_plan.py": "4c011a63aa834b55e9c5d13fb4ea76b131b742356001364b0a508435ba013e27",
32
- "scripts/test_project_memory.py": "2cb85cef01161d881a11d84cfe9ebb72915104e8d1822e4de739f7d00ae6cd59",
33
- "scripts/test_session_start.py": "723f748c0aef461d1051191dbd18f4ea5697a1e0467df73bccb0af8024d17f56",
34
- "scripts/test_skill_invariants.py": "47d116563a37a16b7d25b322020dd730eecac8cf4cfc7916b877df4e729f8373",
35
- "scripts/test_verdict.py": "8620be2f962f900e9fa0f979f80984671baa373de1ce3f29045104d813e71844",
36
- "vision.md": "a19a7d7d5044215b726b66f74e422c9cdedcc856c0a53151f20a6d9840b8f6c4"
37
- }
38
- }
1
+ {
2
+ "_comment": "Generated by shared_files.py --update. IDENTICAL in every distribution of the family: if two copies of this file differ, the spine has diverged. Never edit by hand.",
3
+ "files": {
4
+ "LICENSE": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
5
+ "NOTICE": "8414f424f7c1db19d89d74286fd2d6b1acdd9eedde910fa57a16d6835602e4bd",
6
+ "dispatch.md": "d127057198415f81b921c74e3234a47d5794a2af662eeadaf3dff3cc1739ebc8",
7
+ "docs_site/app.js": "ce9ed4341f7d24b648dee7531700d09f4af07f7e9ffc4be90aff29ecc276f27f",
8
+ "docs_site/index.html": "2b64167470f754e46fa4bb4e73a3d2c34ee632d91fe2f17705a58d18b5018af5",
9
+ "docs_site/strings.js": "440c11830f87602152422766b6d5b84da904150c37b0b6347e2ad53a7fec62b8",
10
+ "docs_site/style.css": "1cbd71eb2e62bb9251ecb62796fe4cecf6a78eecf2f9a7493703290ae55d1244",
11
+ "guides.md": "8fd85ab176d01ad7fc4c72020afa062f37f3c2f6b380f064c20f114c0680b46b",
12
+ "memory.md": "765c8cf34e9161143d6f8b982106251de0ac7f4f6f5d768336f3e4b4b150823e",
13
+ "review.md": "e7eb85f71d79db52e59415e6b3baed15fa9dae4004a6e882190547b3999c778e",
14
+ "routing.md": "5116e8dbd3b215919b897506f28917ac8f1c44cd91f8b64ec901048320e6c1f6",
15
+ "scripts/docs_site.py": "bfc7a521142251b85b229e1873ccf2e375bb5c7f1990cef574e1a78ef14f0091",
16
+ "scripts/entry_point.py": "4a560d4d41db989d3426182f1b24270cbb934b3db0f3246c700578a4ed93089b",
17
+ "scripts/knowledge.py": "6047deb7a2be4cf9ef2949cbcf8a52a8ea61500f4e12d203f1c58076540d50ab",
18
+ "scripts/sdlc_core.py": "582eb73e1b9989bb952c72721bd80b5bd76447955bca85bfd789ea3e94a77afc",
19
+ "scripts/shared_files.py": "d360c6018c602788dd96194bcc0f8e8409b140671857ae50ec15acee882eb51e",
20
+ "scripts/test_audit_refs.py": "5c207be46da566db47069b08373195e748fca19c047418f09cc2f78841dfd9a5",
21
+ "scripts/test_chronology.py": "4a6c1801891bbadfba6bb8309399a4d8485166332b87616df84679eba8305ace",
22
+ "scripts/test_constraints.py": "4be3bbff7032e53b464de2f4e2eb18a824311adb2359c77fb9be065aded09239",
23
+ "scripts/test_design_approval.py": "191856214cb0b4ab8606b57496cf54742568e3bb4bfa0d8f0c64889f2e2d1e63",
24
+ "scripts/test_docs_root.py": "19753e0316c0df63ed321d24b411a9ff36eea2058421fd69fc57e507d75566d0",
25
+ "scripts/test_docs_site.py": "391ec5c635c67f0fbefe458426e352b5b1ef626ae65c099b1e8e1db491d0a5f9",
26
+ "scripts/test_domain_rules.py": "33542b5dc2bb77c05ece55cf0446065cc4226c763ef5e040aa77364f226937bb",
27
+ "scripts/test_drift.py": "c7b7121a8a308b0f3fe12c0c0c346444e632b7aaeff47dc8818b621e5c20cb38",
28
+ "scripts/test_guide_check.py": "19c7a6f009fa1b7f70b0beba059b97fa05e69255f6b2dec8829a87bd9fba283f",
29
+ "scripts/test_merge_safety.py": "8b9241077bf7ecd6dd335907c640044ab38968ff4c9fdc464450e732cb77adf4",
30
+ "scripts/test_migrate.py": "c129823bf4feb0614acd975351632a6ae62d7896a0ef32724419a8e9335e3110",
31
+ "scripts/test_plan.py": "4c011a63aa834b55e9c5d13fb4ea76b131b742356001364b0a508435ba013e27",
32
+ "scripts/test_project_memory.py": "2cb85cef01161d881a11d84cfe9ebb72915104e8d1822e4de739f7d00ae6cd59",
33
+ "scripts/test_session_start.py": "723f748c0aef461d1051191dbd18f4ea5697a1e0467df73bccb0af8024d17f56",
34
+ "scripts/test_skill_invariants.py": "4c0f5f2960f177fac58d4b533df878880de800d909ea3f522883da60dad3c3dd",
35
+ "scripts/test_verdict.py": "8620be2f962f900e9fa0f979f80984671baa373de1ce3f29045104d813e71844",
36
+ "vision.md": "a19a7d7d5044215b726b66f74e422c9cdedcc856c0a53151f20a6d9840b8f6c4"
37
+ }
38
+ }
@@ -71,6 +71,14 @@ Context (agent): "the question it answered, quoted"
71
71
  entered only as `[NOT VERBATIM — from <source>]`, never as a quote.
72
72
  - **Unattended**: R1 quotes the trigger that started the unit, labelled as such.
73
73
  Declared assumptions are not owner statements and never enter the record.
74
+ - **Labelled entries** (r41): a decision taken under the owner's delegation is
75
+ `### R<n> — YYYY-MM-DD — decided under delegation R<m> — <what it settles>`, citing
76
+ the delegation entry (in Hybrid also the devPNT proposal it accepted); it is the
77
+ deciding session's choice, never an owner statement, and never changes an earlier
78
+ owner decision — that goes to the owner as a question. Owner words received for a
79
+ unit whose record another session writes are
80
+ `### R<n> — YYYY-MM-DD — received for <unit> — <what it settles>`, then sent to that
81
+ record's writer, who enters them verbatim citing this entry.
74
82
  - **Design approval** (L3): the owner's approval of the design is an entry whose
75
83
  label begins `design approved`; a no or a correction takes its own label
76
84
  (`review.md` §When a review is due).
@@ -1,308 +1,308 @@
1
- # Writing a Vision that a cold reviewer can actually apply
2
-
3
- Support file for `ai_docs/vision/project_vision.md`, `vision/features/VISION_*.md`
4
- and (Hybrid) the devPNT M-VISION. Read it when writing or amending any of them.
5
- The template lives in `templates.md`; this file is the *why it works*.
6
-
7
- **The problem this solves.** A Vision's job is to be read cold — by a later
8
- session, by a reviewer, by another agent — and to produce a ruling on a proposed
9
- change. Most Visions cannot do that: they read as intent to whoever wrote them and
10
- as ambiguity to everyone else, and the gap is invisible until something wrong gets
11
- through. Writing one and then discovering the holes by adversarial rounds works,
12
- but it costs several rewrites. The checklist below exists so a Vision is
13
- verifiable on the **first** draft.
14
-
15
- Everything here was derived empirically: six blind adversarial rounds against one
16
- real Vision, reviewers with no repository access, ~25 attack proposals. Rules that
17
- survived every attack and rules that fell were compared for structure. What
18
- follows is that difference.
19
-
20
- ## What a Vision IS
21
-
22
- A Vision states **what is to be obtained — the benefit — while leaving the most
23
- degrees of freedom possible. It binds nothing that does not obstruct that
24
- benefit.**
25
-
26
- Four consequences, each operational:
27
-
28
- - **State the benefit, not the mechanism.** What the actor *obtains*, never how —
29
- and concrete enough that an obstacle to it is *recognizable*. This is the test a
30
- North Star must pass: from "best-in-class" no constraint is derivable and none
31
- is refutable, because nothing recognizably obstructs it. From "everything the
32
- methodology produces stays usable in full without paying", a metering ban
33
- follows and can be checked.
34
- - **The deletion test — the generative rule for every constraint.** Remove the
35
- rule: is the benefit still reachable? If **yes**, delete the rule — it was
36
- spending a degree of freedom on nothing. If **no**, keep it, and its sentence
37
- must name the obstacle it removes. This is also the stop rule: when nothing on
38
- the table would obstruct the benefit, the Vision needs no new rule.
39
- - **Constraints accumulate as the work reveals obstacles.** The first draft is the
40
- benefit plus the few constraints already known — usually almost none, and that
41
- is correct (`DRAFT` informs, `APPROVED` binds). Each constraint added later
42
- *sharpens* the Vision without shrinking it more than the obstacle requires.
43
- - **A constraint never obstructs the Vision.** A proposed rule that conflicts with
44
- the benefit is not a constraint to negotiate in place — it is a Vision
45
- **amendment**, and amending is the owner's decision, never a side effect of a
46
- downstream edit.
47
-
48
- Extracting the benefit from a discussion is real work: a discourse arrives with
49
- solutions, preferences and constraints tangled together, and the Vision is the
50
- distilled benefit only. The owner interview below asks for it — and a mechanism
51
- ("a dashboard") is not an acceptable answer to a benefit question ("never lose
52
- the thread"); ask again until the answer names what the actor obtains.
53
-
54
- The deletion test decides **which** rules exist. Everything below decides **how**
55
- to write a rule so it holds once you know it must exist.
56
-
57
- ## The owner interview (before any Vision text)
58
-
59
- A Vision is the **owner's** statement of what is to be obtained. Nothing in this
60
- file after this section can detect a Vision the author wrote alone: the blind
61
- check (§6) tests whether the rules survive a hostile reader, never whether they
62
- are the owner's. So every Vision is born from an interview of the owner, and its
63
- north star is approved **before** a line of it is drafted.
64
-
65
- **Scope — every Vision, in every mode:** `project_vision.md`, a feature
66
- `VISION_*.md`, a lens Vision (course, marketing), and the devPNT M-VISION in
67
- Hybrid; also an ANALYSIS `## Feature Vision` that states a benefit no existing
68
- Vision names (one that cites an approved Vision's benefit is alignment, not a
69
- Vision). An amendment re-runs the questions whose answers it changes, and Gate 1
70
- again when the north star changes. **There is no skip path**: one sentence from
71
- the user plus a spike is not a complete spec — that exact shortcut produced a
72
- Vision with an imported prohibition, no north star and a missing actor, and four
73
- downstream artifacts built on it (field case, 2026-10). A reply already on record
74
- in the owner's words is read back for confirmation, never re-asked as an open
75
- question; an answer the author derived (from a spike, the code, another Vision)
76
- is never an answer — it is a proposal the owner confirms or rejects.
77
-
78
- **The questions, in this order.** Each is repeated until its answer has the form
79
- stated; the wrong form is named so it can be recognized and asked again.
80
-
81
- | # | Question | Right form | Wrong form — ask again |
82
- |---|---|---|---|
83
- | 1 | **Problem** — which concrete case today does not get the expected result? | one real case, with an example | a missing feature ("there is no drawing tool") |
84
- | 2 | **Benefit** — once solved, who obtains what? | a result for a named actor | a function or mechanism ("a renderer") |
85
- | 3 | **Actors** — who receives the benefit, who acts, who has the initiative? | all three named, initiative included | only the one who asks — the actor who acts unprompted goes missing |
86
- | 4 | **Success** — what observable signal shows the benefit arrived? | a signal with its starting value | a feeling, or a signal with no baseline |
87
- | 5 | **Boundaries** — what does this NOT pursue? | scope excluded from this work | a ban on behaviour ("X must never happen") — that is a constraint (6) or an obligation (2, 4) |
88
- | 6 | **Known constraints** — each with its provenance: owner, a quoted line of a higher Vision, or author inference | every constraint sourced; every inference asked here | a constraint written in silently, or imported from a higher Vision without asking whether it applies here |
89
-
90
- A **Non-Goal bounds scope**: it rejects a proposal that pursues what it names; it
91
- is not a ban on behaviour that already exists, and it creates no obligation.
92
- Obligations come from the Benefit and the Success Signals.
93
-
94
- **Gate 1 — north star.** One or two sentences, in the owner's words, explicitly
95
- approved by the owner. The author may compose a wording from the answers; the
96
- approval is of that exact text, recorded verbatim in the request record
97
- (`audit/requests/REQUEST_[unit].md`; a Vision written outside any unit, such as
98
- the project Vision after bootstrap, records its interview in
99
- `audit/requests/REQUEST_project_vision.md`). **Without it no Vision text is written** —
100
- not a DRAFT, not a reconstruction: bootstrap writes a stub awaiting the
101
- interview, and what the agent observed goes to `strategic/`.
102
- Unattended, the unit is `BLOCKED on the user` (`elicitation.md` Unattended path).
103
-
104
- **Gate 2 — the full Vision**, written from the answers. Every section traces to
105
- an answer or a stated provenance (`REQUEST_[unit].md#R<n>`); every rule carries
106
- its provenance; an author inference the owner did not confirm is not in it. The
107
- blind check (§6) verifies that it holds; the approved north star verifies that it
108
- is the owner's. Promotion to APPROVED still needs the owner's explicit
109
- confirmation — Gate 2 is that confirmation, given on the written text.
110
-
111
- ## 0. The one-line test
112
-
113
- > Could a reader who has never seen this project rule ACCEPT or REJECT on a
114
- > proposed change, quoting one line of this document, without asking anyone
115
- > anything?
116
-
117
- If a section cannot contribute to that, it is background — keep it if it helps a
118
- human, but know it carries no gate weight.
119
-
120
- ## 1. The nine properties of a rule that holds
121
-
122
- Each is stated as a drafting action, with the attack it defeats.
123
-
124
- 1. **Key the rule to a property observable in the artifact — never to intent,
125
- commitment, plan or purpose.** Test while drafting: *could I verify compliance
126
- by reading the diff, without asking the author what they meant?*
127
- → Defeats: the proposer simply promises the opposite. A rule that forbids
128
- "committing to track someone's format" is satisfied by saying "we commit to
129
- nothing"; a rule that forbids "code in this repo that parses a format we do
130
- not define" is not.
131
- 2. **Write it as one yes/no question with BOTH branches answered.**
132
- → Defeats: a gate that can only reject. A prohibition-only Vision rejects the
133
- work it wants (new client support, packaging, bug fixes) and is abandoned.
134
- 3. **Phrase the question counterfactually about capability, not about the status
135
- quo** — "could the user obtain this…", not "does the product currently do it".
136
- → Defeats: "we never shipped it, so nothing is being taken away."
137
- 4. **Enumerate the near-miss verbs, not just the headline one** — charge / count /
138
- cap / condition / gate / degrade / delay / require-signup.
139
- → Defeats: the soft form. A generous cap, a free-but-registered tier, a
140
- zero-cost counter — all are the same harm and none is the headline verb.
141
- 5. **Define every term the rule turns on by EFFECT, and close the list.** A
142
- definition that enumerates mechanisms is a list of the ways you already thought
143
- of; end it with "…and anything else the user must give, accept or obtain from
144
- us."
145
- → Defeats: the third mechanism. "Charge = payment or account" is walked past by
146
- "requires opting into telemetry".
147
- 6. **Enumerate the FORMS a violation can take, then state a closure rule** —
148
- "anything not named here is out unless it is X".
149
- → Defeats: the unnamed variant. "A step, a required field, a check, or a cost
150
- that varies but is never zero" kills "auto-filled, zero human input", because
151
- the form is still a required field.
152
- 7. **State the subject predicate of any enumerated test in checkable terms.** If
153
- the test applies to "a record of work", say what makes something one.
154
- → Defeats: denying the subject. The proposer agrees with every item on your
155
- list and asserts the list does not apply.
156
- 8. **Give one IN and one OUT example on the same axis, differing in a single
157
- variable.** The pair carries the discriminator; without it the reader invents
158
- one.
159
- → Defeats: the borderline case, which is where every real argument happens.
160
- 9. **Name the re-descriptions you expect, inside the rule's own sentence** —
161
- "in any presentation", "however the code got here", "not by a component that
162
- does not exist yet", "storing it as Markdown changes nothing".
163
- → Defeats: relabeling. A board called a "view" is still a board.
164
-
165
- ## 2. The five clauses a Vision needs around its rules
166
-
167
- Rules do not hold alone. These structural clauses were the difference between a
168
- rule that survived and the same rule leaking.
169
-
170
- - **Supremacy clause** — on any rule a second layer, product, tier or future
171
- component could route around: *"these bind the product as a whole; shipping a
172
- forbidden thing in the paid layer does not put it out of reach."*
173
- - **Exceptions attached to the rule they limit, in the same bullet, phrased
174
- affirmatively.** An exception in another section is a leak; an exception phrased
175
- only as a negation produces CANNOT DECIDE. *"This rule does not reach X;
176
- supporting one more X is squarely wanted."*
177
- - **Anti-abuse clause on every exemption, naming the only permitted outcomes.**
178
- A maintenance exemption without one lets any forbidden thing that already
179
- shipped be maintained forever, and any new one be framed as a fix to it:
180
- *"work framed as a fix to something that should never have shipped is not
181
- exempt — it is the removal of that thing, or it is out."*
182
- - **Stated defaults, per path including the exempt path** — "anything unreached is
183
- out"; "an exempt fix that preserves a forbidden thing is out". A test that says
184
- "admitted only if" gives new capability a default; exempt work has none unless
185
- you write one.
186
- - **Precedence, when two sentences can both apply.** Two adjacent statements
187
- pointing opposite ways are decided by whichever the reader reaches first.
188
-
189
- ## 3. Only prohibitions can reject; only positives can admit
190
-
191
- This is the most common structural failure and it is invisible from the inside.
192
-
193
- - **A Goal cannot reject anything.** If the admission test says "advances a Goal
194
- and violates no Non-Goal", then rejection power lives *only* in the Non-Goals
195
- (and the Invariants, where the Vision has them).
196
- - **A Non-Goal rejects scope, not behaviour.** It turns away a proposal that
197
- pursues what it names; it never forbids a behaviour the product already has, and
198
- it never creates an obligation — obligations come from the Benefit and the
199
- Success Signals. A behaviour the product must never have is an Invariant, or a
200
- constraint with its provenance (§The owner interview, question 6). Written as a
201
- Non-Goal, it reads as a ban the owner never stated.
202
- A Goal that says "scale cost to risk in both directions" cannot stop cost
203
- inflation — you need a prohibition for that.
204
- - **A prohibition scoped to one rung of your own scale protects only that rung.**
205
- Banning ceremony on trivial edits invites the same ceremony one level up.
206
- - **A criterion phrased as an already-true state cannot be advanced.** "The check
207
- is CLEAN at every closure" is a state, so "advances this signal" is meaningless
208
- and any proposal citing it is undecidable. Give every positive criterion a
209
- **baseline with headroom**: a current value and date, or an explicit list of
210
- improvement categories that count.
211
- - **Give recurring legitimate work an explicit authorization clause**, or it will
212
- be rejected by your own test: packaging and installation, the product's own
213
- tests, reducing what the agent must read, supporting one more client. Each needs
214
- a home in the positive sources — otherwise "moves nothing this document commits
215
- to" fires on exactly the maintenance the product needs.
216
-
217
- ## 4. Minimum operable sections
218
-
219
- A Vision is a gate. These are the load-bearing parts; anything else is
220
- orientation for humans and should be recognized as such.
221
-
222
- | Section | Must contain | Enables |
223
- |---|---|---|
224
- | Authority & scope | what it binds (all layers, tiers, future components), that packaging is irrelevant, precedence among its own sections, the approval line | both |
225
- | Defaults | the default ruling for anything unreached, stated per path | both |
226
- | Admission test | one sentence naming *exactly which* sections are positive sources and *exactly which* are prohibitions | ACCEPT |
227
- | Positive sources | Goals / Actor commitments / Success Signals — each with a baseline and headroom, checkable against a named artifact | ACCEPT |
228
- | Prohibitions | property-based, closed enumerations, named re-descriptions, in/out pairs | REJECT |
229
- | Invariants | the decision question with both branches answered, plus an anti-laundering clause | both |
230
- | Exemptions | each with its anti-abuse clause | prevents laundering |
231
- | Definitions & imported facts | terms defined by effect; **any fact from another document a ruling depends on, restated here** with the reason | standalone use |
232
- | Pointers | what deliberately lives elsewhere, so an absence reads as intentional | prevents false CANNOT DECIDE |
233
-
234
- **Imported facts matter more than they look.** If a ruling needs your triage
235
- levels, your risk tiers or your lifecycle states, restate the boundaries in the
236
- Vision — routing the reader to another file breaks the cold-read premise the
237
- whole gate rests on. Keep the procedure elsewhere; bring the boundaries here.
238
-
239
- **Keep competitive positioning OUT.** A Vision defined by comparison to another
240
- product rots silently: the comparison target moves and no one edits your document.
241
- Put it in a dated snapshot elsewhere and point to it.
242
-
243
- ## 5. What no wording can fix — use a mechanism instead
244
-
245
- Five failure classes are structural. Prose cannot close them; do not try.
246
-
247
- | Failure | Mechanism |
248
- |---|---|
249
- | "Advances a Goal" is a claim about the world, not the text | Measure it: run the ruling battery before and after, admit on measured improvement |
250
- | **Cumulative ratchet** — each addition defensible, the sum is the ceremony the Vision forbids | A measured budget: steps per level, tokens the agent loads, artifacts per change; add-one-remove-one |
251
- | Facts the proposal never states ("a quota of *what*?") | A proposal template that demands them, and a stated rule that omission resolves against the proposal |
252
- | Structural compliance without truth — every rule satisfied, the artifact still wrong | Independent review; a Vision cannot detect this |
253
- | An accepted change silently falsifying the Vision | Require the amendment in the same change; check cross-document facts mechanically |
254
-
255
- And the sixth, which is why this file exists:
256
-
257
- **Adversarial re-description is unbounded.** Every patch names the evasions seen so
258
- far; a new one always exists. Patch-after-defeat is a treadmill. The only thing
259
- that converts it into a ratchet is a **standing battery**: keep every attack
260
- proposal that ever worked as a fixture and re-run the whole set against every
261
- Vision edit. A Vision without a battery decays the moment someone motivated reads
262
- it.
263
-
264
- ## 6. The blind check (procedure)
265
-
266
- Run this **before promoting any Vision to APPROVED, and before any amendment of an
267
- approved one**. It runs on a Vision that already passed Gate 1 (§The owner
268
- interview): it tests whether the rules hold against a hostile reader, and it
269
- cannot see whether they are the owner's — a Vision the author wrote alone passes
270
- it cleanly. Not for DRAFT edits — promotion is when authority is granted, and
271
- it is rare, so the cost lands where it buys most.
272
-
273
- 1. **Give the reviewer the document text and nothing else.** Not a path — the
274
- text, pasted. Forbid opening files, searching, and web access explicitly. A
275
- term the reviewer cannot resolve is then a property of the document, not of
276
- their tooling.
277
- 2. **Fresh context, and a different model from the author where the client allows
278
- it.** Author self-review is structurally blind to its own omissions.
279
- 3. **Hand them a battery, not an open question.** Concrete proposals to rule on:
280
- - the standing fixtures (every attack that ever worked — see §5);
281
- - **reject-side** proposals aimed at each prohibition;
282
- - **accept-side** proposals the document plainly wants (a bug fix, packaging
283
- work, supporting one more client, a documentation improvement). *A gate that
284
- can only reject is half a gate, and the accept side is where most Visions
285
- fail without anyone noticing.*
286
- 4. **Ask for the mechanism, not just the verdict.** For each ruling: the exact
287
- quoted line, or precisely what the document fails to say. Then: *which rules
288
- could you not get around, and what structural property defeated you?* That
289
- answer is worth more than the findings.
290
- 5. **Ask for rewords.** "Slip a bad proposal past this document, including one
291
- dressed in its own approving vocabulary." A rule nobody can reword is done; a
292
- rule that falls to the first attempt was never a rule.
293
- 6. **Every finding is answered — fixed, or refused with reasoning** (`review.md`
294
- §Receiving). Add every successful reword to the standing battery, whether or
295
- not you fix it this round.
296
-
297
- Three lenses are worth running for a product-level Vision: **comprehension** (what
298
- is this, what could you not resolve), **gate operability** (the battery above),
299
- **durability** (what rots, what is unfalsifiable, what is a time bomb). For a
300
- feature-level Vision or an M-VISION, the gate lens alone is usually enough.
301
-
302
- ## 7. Cost, honestly
303
-
304
- A first draft written against §1–§4 will still have findings — the checklist
305
- removes the structural classes, not judgement errors. Expect one blind round to
306
- find real things and a second to confirm. What it should NOT take is five rounds
307
- of discovering the same class of defect in a new disguise; that is the specific
308
- waste this file exists to prevent.
1
+ # Writing a Vision that a cold reviewer can actually apply
2
+
3
+ Support file for `ai_docs/vision/project_vision.md`, `vision/features/VISION_*.md`
4
+ and (Hybrid) the devPNT M-VISION. Read it when writing or amending any of them.
5
+ The template lives in `templates.md`; this file is the *why it works*.
6
+
7
+ **The problem this solves.** A Vision's job is to be read cold — by a later
8
+ session, by a reviewer, by another agent — and to produce a ruling on a proposed
9
+ change. Most Visions cannot do that: they read as intent to whoever wrote them and
10
+ as ambiguity to everyone else, and the gap is invisible until something wrong gets
11
+ through. Writing one and then discovering the holes by adversarial rounds works,
12
+ but it costs several rewrites. The checklist below exists so a Vision is
13
+ verifiable on the **first** draft.
14
+
15
+ Everything here was derived empirically: six blind adversarial rounds against one
16
+ real Vision, reviewers with no repository access, ~25 attack proposals. Rules that
17
+ survived every attack and rules that fell were compared for structure. What
18
+ follows is that difference.
19
+
20
+ ## What a Vision IS
21
+
22
+ A Vision states **what is to be obtained — the benefit — while leaving the most
23
+ degrees of freedom possible. It binds nothing that does not obstruct that
24
+ benefit.**
25
+
26
+ Four consequences, each operational:
27
+
28
+ - **State the benefit, not the mechanism.** What the actor *obtains*, never how —
29
+ and concrete enough that an obstacle to it is *recognizable*. This is the test a
30
+ North Star must pass: from "best-in-class" no constraint is derivable and none
31
+ is refutable, because nothing recognizably obstructs it. From "everything the
32
+ methodology produces stays usable in full without paying", a metering ban
33
+ follows and can be checked.
34
+ - **The deletion test — the generative rule for every constraint.** Remove the
35
+ rule: is the benefit still reachable? If **yes**, delete the rule — it was
36
+ spending a degree of freedom on nothing. If **no**, keep it, and its sentence
37
+ must name the obstacle it removes. This is also the stop rule: when nothing on
38
+ the table would obstruct the benefit, the Vision needs no new rule.
39
+ - **Constraints accumulate as the work reveals obstacles.** The first draft is the
40
+ benefit plus the few constraints already known — usually almost none, and that
41
+ is correct (`DRAFT` informs, `APPROVED` binds). Each constraint added later
42
+ *sharpens* the Vision without shrinking it more than the obstacle requires.
43
+ - **A constraint never obstructs the Vision.** A proposed rule that conflicts with
44
+ the benefit is not a constraint to negotiate in place — it is a Vision
45
+ **amendment**, and amending is the owner's decision, never a side effect of a
46
+ downstream edit.
47
+
48
+ Extracting the benefit from a discussion is real work: a discourse arrives with
49
+ solutions, preferences and constraints tangled together, and the Vision is the
50
+ distilled benefit only. The owner interview below asks for it — and a mechanism
51
+ ("a dashboard") is not an acceptable answer to a benefit question ("never lose
52
+ the thread"); ask again until the answer names what the actor obtains.
53
+
54
+ The deletion test decides **which** rules exist. Everything below decides **how**
55
+ to write a rule so it holds once you know it must exist.
56
+
57
+ ## The owner interview (before any Vision text)
58
+
59
+ A Vision is the **owner's** statement of what is to be obtained. Nothing in this
60
+ file after this section can detect a Vision the author wrote alone: the blind
61
+ check (§6) tests whether the rules survive a hostile reader, never whether they
62
+ are the owner's. So every Vision is born from an interview of the owner, and its
63
+ north star is approved **before** a line of it is drafted.
64
+
65
+ **Scope — every Vision, in every mode:** `project_vision.md`, a feature
66
+ `VISION_*.md`, a lens Vision (course, marketing), and the devPNT M-VISION in
67
+ Hybrid; also an ANALYSIS `## Feature Vision` that states a benefit no existing
68
+ Vision names (one that cites an approved Vision's benefit is alignment, not a
69
+ Vision). An amendment re-runs the questions whose answers it changes, and Gate 1
70
+ again when the north star changes. **There is no skip path**: one sentence from
71
+ the user plus a spike is not a complete spec — that exact shortcut produced a
72
+ Vision with an imported prohibition, no north star and a missing actor, and four
73
+ downstream artifacts built on it (field case, 2026-10). A reply already on record
74
+ in the owner's words is read back for confirmation, never re-asked as an open
75
+ question; an answer the author derived (from a spike, the code, another Vision)
76
+ is never an answer — it is a proposal the owner confirms or rejects.
77
+
78
+ **The questions, in this order.** Each is repeated until its answer has the form
79
+ stated; the wrong form is named so it can be recognized and asked again.
80
+
81
+ | # | Question | Right form | Wrong form — ask again |
82
+ |---|---|---|---|
83
+ | 1 | **Problem** — which concrete case today does not get the expected result? | one real case, with an example | a missing feature ("there is no drawing tool") |
84
+ | 2 | **Benefit** — once solved, who obtains what? | a result for a named actor | a function or mechanism ("a renderer") |
85
+ | 3 | **Actors** — who receives the benefit, who acts, who has the initiative? | all three named, initiative included | only the one who asks — the actor who acts unprompted goes missing |
86
+ | 4 | **Success** — what observable signal shows the benefit arrived? | a signal with its starting value | a feeling, or a signal with no baseline |
87
+ | 5 | **Boundaries** — what does this NOT pursue? | scope excluded from this work | a ban on behaviour ("X must never happen") — that is a constraint (6) or an obligation (2, 4) |
88
+ | 6 | **Known constraints** — each with its provenance: owner, a quoted line of a higher Vision, or author inference | every constraint sourced; every inference asked here | a constraint written in silently, or imported from a higher Vision without asking whether it applies here |
89
+
90
+ A **Non-Goal bounds scope**: it rejects a proposal that pursues what it names; it
91
+ is not a ban on behaviour that already exists, and it creates no obligation.
92
+ Obligations come from the Benefit and the Success Signals.
93
+
94
+ **Gate 1 — north star.** One or two sentences, in the owner's words, explicitly
95
+ approved by the owner. The author may compose a wording from the answers; the
96
+ approval is of that exact text, recorded verbatim in the request record
97
+ (`audit/requests/REQUEST_[unit].md`; a Vision written outside any unit, such as
98
+ the project Vision after bootstrap, records its interview in
99
+ `audit/requests/REQUEST_project_vision.md`). **Without it no Vision text is written** —
100
+ not a DRAFT, not a reconstruction: bootstrap writes a stub awaiting the
101
+ interview, and what the agent observed goes to `strategic/`.
102
+ Unattended, the unit is `BLOCKED on the user` (`elicitation.md` Unattended path).
103
+
104
+ **Gate 2 — the full Vision**, written from the answers. Every section traces to
105
+ an answer or a stated provenance (`REQUEST_[unit].md#R<n>`); every rule carries
106
+ its provenance; an author inference the owner did not confirm is not in it. The
107
+ blind check (§6) verifies that it holds; the approved north star verifies that it
108
+ is the owner's. Promotion to APPROVED still needs the owner's explicit
109
+ confirmation — Gate 2 is that confirmation, given on the written text.
110
+
111
+ ## 0. The one-line test
112
+
113
+ > Could a reader who has never seen this project rule ACCEPT or REJECT on a
114
+ > proposed change, quoting one line of this document, without asking anyone
115
+ > anything?
116
+
117
+ If a section cannot contribute to that, it is background — keep it if it helps a
118
+ human, but know it carries no gate weight.
119
+
120
+ ## 1. The nine properties of a rule that holds
121
+
122
+ Each is stated as a drafting action, with the attack it defeats.
123
+
124
+ 1. **Key the rule to a property observable in the artifact — never to intent,
125
+ commitment, plan or purpose.** Test while drafting: *could I verify compliance
126
+ by reading the diff, without asking the author what they meant?*
127
+ → Defeats: the proposer simply promises the opposite. A rule that forbids
128
+ "committing to track someone's format" is satisfied by saying "we commit to
129
+ nothing"; a rule that forbids "code in this repo that parses a format we do
130
+ not define" is not.
131
+ 2. **Write it as one yes/no question with BOTH branches answered.**
132
+ → Defeats: a gate that can only reject. A prohibition-only Vision rejects the
133
+ work it wants (new client support, packaging, bug fixes) and is abandoned.
134
+ 3. **Phrase the question counterfactually about capability, not about the status
135
+ quo** — "could the user obtain this…", not "does the product currently do it".
136
+ → Defeats: "we never shipped it, so nothing is being taken away."
137
+ 4. **Enumerate the near-miss verbs, not just the headline one** — charge / count /
138
+ cap / condition / gate / degrade / delay / require-signup.
139
+ → Defeats: the soft form. A generous cap, a free-but-registered tier, a
140
+ zero-cost counter — all are the same harm and none is the headline verb.
141
+ 5. **Define every term the rule turns on by EFFECT, and close the list.** A
142
+ definition that enumerates mechanisms is a list of the ways you already thought
143
+ of; end it with "…and anything else the user must give, accept or obtain from
144
+ us."
145
+ → Defeats: the third mechanism. "Charge = payment or account" is walked past by
146
+ "requires opting into telemetry".
147
+ 6. **Enumerate the FORMS a violation can take, then state a closure rule** —
148
+ "anything not named here is out unless it is X".
149
+ → Defeats: the unnamed variant. "A step, a required field, a check, or a cost
150
+ that varies but is never zero" kills "auto-filled, zero human input", because
151
+ the form is still a required field.
152
+ 7. **State the subject predicate of any enumerated test in checkable terms.** If
153
+ the test applies to "a record of work", say what makes something one.
154
+ → Defeats: denying the subject. The proposer agrees with every item on your
155
+ list and asserts the list does not apply.
156
+ 8. **Give one IN and one OUT example on the same axis, differing in a single
157
+ variable.** The pair carries the discriminator; without it the reader invents
158
+ one.
159
+ → Defeats: the borderline case, which is where every real argument happens.
160
+ 9. **Name the re-descriptions you expect, inside the rule's own sentence** —
161
+ "in any presentation", "however the code got here", "not by a component that
162
+ does not exist yet", "storing it as Markdown changes nothing".
163
+ → Defeats: relabeling. A board called a "view" is still a board.
164
+
165
+ ## 2. The five clauses a Vision needs around its rules
166
+
167
+ Rules do not hold alone. These structural clauses were the difference between a
168
+ rule that survived and the same rule leaking.
169
+
170
+ - **Supremacy clause** — on any rule a second layer, product, tier or future
171
+ component could route around: *"these bind the product as a whole; shipping a
172
+ forbidden thing in the paid layer does not put it out of reach."*
173
+ - **Exceptions attached to the rule they limit, in the same bullet, phrased
174
+ affirmatively.** An exception in another section is a leak; an exception phrased
175
+ only as a negation produces CANNOT DECIDE. *"This rule does not reach X;
176
+ supporting one more X is squarely wanted."*
177
+ - **Anti-abuse clause on every exemption, naming the only permitted outcomes.**
178
+ A maintenance exemption without one lets any forbidden thing that already
179
+ shipped be maintained forever, and any new one be framed as a fix to it:
180
+ *"work framed as a fix to something that should never have shipped is not
181
+ exempt — it is the removal of that thing, or it is out."*
182
+ - **Stated defaults, per path including the exempt path** — "anything unreached is
183
+ out"; "an exempt fix that preserves a forbidden thing is out". A test that says
184
+ "admitted only if" gives new capability a default; exempt work has none unless
185
+ you write one.
186
+ - **Precedence, when two sentences can both apply.** Two adjacent statements
187
+ pointing opposite ways are decided by whichever the reader reaches first.
188
+
189
+ ## 3. Only prohibitions can reject; only positives can admit
190
+
191
+ This is the most common structural failure and it is invisible from the inside.
192
+
193
+ - **A Goal cannot reject anything.** If the admission test says "advances a Goal
194
+ and violates no Non-Goal", then rejection power lives *only* in the Non-Goals
195
+ (and the Invariants, where the Vision has them).
196
+ - **A Non-Goal rejects scope, not behaviour.** It turns away a proposal that
197
+ pursues what it names; it never forbids a behaviour the product already has, and
198
+ it never creates an obligation — obligations come from the Benefit and the
199
+ Success Signals. A behaviour the product must never have is an Invariant, or a
200
+ constraint with its provenance (§The owner interview, question 6). Written as a
201
+ Non-Goal, it reads as a ban the owner never stated.
202
+ A Goal that says "scale cost to risk in both directions" cannot stop cost
203
+ inflation — you need a prohibition for that.
204
+ - **A prohibition scoped to one rung of your own scale protects only that rung.**
205
+ Banning ceremony on trivial edits invites the same ceremony one level up.
206
+ - **A criterion phrased as an already-true state cannot be advanced.** "The check
207
+ is CLEAN at every closure" is a state, so "advances this signal" is meaningless
208
+ and any proposal citing it is undecidable. Give every positive criterion a
209
+ **baseline with headroom**: a current value and date, or an explicit list of
210
+ improvement categories that count.
211
+ - **Give recurring legitimate work an explicit authorization clause**, or it will
212
+ be rejected by your own test: packaging and installation, the product's own
213
+ tests, reducing what the agent must read, supporting one more client. Each needs
214
+ a home in the positive sources — otherwise "moves nothing this document commits
215
+ to" fires on exactly the maintenance the product needs.
216
+
217
+ ## 4. Minimum operable sections
218
+
219
+ A Vision is a gate. These are the load-bearing parts; anything else is
220
+ orientation for humans and should be recognized as such.
221
+
222
+ | Section | Must contain | Enables |
223
+ |---|---|---|
224
+ | Authority & scope | what it binds (all layers, tiers, future components), that packaging is irrelevant, precedence among its own sections, the approval line | both |
225
+ | Defaults | the default ruling for anything unreached, stated per path | both |
226
+ | Admission test | one sentence naming *exactly which* sections are positive sources and *exactly which* are prohibitions | ACCEPT |
227
+ | Positive sources | Goals / Actor commitments / Success Signals — each with a baseline and headroom, checkable against a named artifact | ACCEPT |
228
+ | Prohibitions | property-based, closed enumerations, named re-descriptions, in/out pairs | REJECT |
229
+ | Invariants | the decision question with both branches answered, plus an anti-laundering clause | both |
230
+ | Exemptions | each with its anti-abuse clause | prevents laundering |
231
+ | Definitions & imported facts | terms defined by effect; **any fact from another document a ruling depends on, restated here** with the reason | standalone use |
232
+ | Pointers | what deliberately lives elsewhere, so an absence reads as intentional | prevents false CANNOT DECIDE |
233
+
234
+ **Imported facts matter more than they look.** If a ruling needs your triage
235
+ levels, your risk tiers or your lifecycle states, restate the boundaries in the
236
+ Vision — routing the reader to another file breaks the cold-read premise the
237
+ whole gate rests on. Keep the procedure elsewhere; bring the boundaries here.
238
+
239
+ **Keep competitive positioning OUT.** A Vision defined by comparison to another
240
+ product rots silently: the comparison target moves and no one edits your document.
241
+ Put it in a dated snapshot elsewhere and point to it.
242
+
243
+ ## 5. What no wording can fix — use a mechanism instead
244
+
245
+ Five failure classes are structural. Prose cannot close them; do not try.
246
+
247
+ | Failure | Mechanism |
248
+ |---|---|
249
+ | "Advances a Goal" is a claim about the world, not the text | Measure it: run the ruling battery before and after, admit on measured improvement |
250
+ | **Cumulative ratchet** — each addition defensible, the sum is the ceremony the Vision forbids | A measured budget: steps per level, tokens the agent loads, artifacts per change; add-one-remove-one |
251
+ | Facts the proposal never states ("a quota of *what*?") | A proposal template that demands them, and a stated rule that omission resolves against the proposal |
252
+ | Structural compliance without truth — every rule satisfied, the artifact still wrong | Independent review; a Vision cannot detect this |
253
+ | An accepted change silently falsifying the Vision | Require the amendment in the same change; check cross-document facts mechanically |
254
+
255
+ And the sixth, which is why this file exists:
256
+
257
+ **Adversarial re-description is unbounded.** Every patch names the evasions seen so
258
+ far; a new one always exists. Patch-after-defeat is a treadmill. The only thing
259
+ that converts it into a ratchet is a **standing battery**: keep every attack
260
+ proposal that ever worked as a fixture and re-run the whole set against every
261
+ Vision edit. A Vision without a battery decays the moment someone motivated reads
262
+ it.
263
+
264
+ ## 6. The blind check (procedure)
265
+
266
+ Run this **before promoting any Vision to APPROVED, and before any amendment of an
267
+ approved one**. It runs on a Vision that already passed Gate 1 (§The owner
268
+ interview): it tests whether the rules hold against a hostile reader, and it
269
+ cannot see whether they are the owner's — a Vision the author wrote alone passes
270
+ it cleanly. Not for DRAFT edits — promotion is when authority is granted, and
271
+ it is rare, so the cost lands where it buys most.
272
+
273
+ 1. **Give the reviewer the document text and nothing else.** Not a path — the
274
+ text, pasted. Forbid opening files, searching, and web access explicitly. A
275
+ term the reviewer cannot resolve is then a property of the document, not of
276
+ their tooling.
277
+ 2. **Fresh context, and a different model from the author where the client allows
278
+ it.** Author self-review is structurally blind to its own omissions.
279
+ 3. **Hand them a battery, not an open question.** Concrete proposals to rule on:
280
+ - the standing fixtures (every attack that ever worked — see §5);
281
+ - **reject-side** proposals aimed at each prohibition;
282
+ - **accept-side** proposals the document plainly wants (a bug fix, packaging
283
+ work, supporting one more client, a documentation improvement). *A gate that
284
+ can only reject is half a gate, and the accept side is where most Visions
285
+ fail without anyone noticing.*
286
+ 4. **Ask for the mechanism, not just the verdict.** For each ruling: the exact
287
+ quoted line, or precisely what the document fails to say. Then: *which rules
288
+ could you not get around, and what structural property defeated you?* That
289
+ answer is worth more than the findings.
290
+ 5. **Ask for rewords.** "Slip a bad proposal past this document, including one
291
+ dressed in its own approving vocabulary." A rule nobody can reword is done; a
292
+ rule that falls to the first attempt was never a rule.
293
+ 6. **Every finding is answered — fixed, or refused with reasoning** (`review.md`
294
+ §Receiving). Add every successful reword to the standing battery, whether or
295
+ not you fix it this round.
296
+
297
+ Three lenses are worth running for a product-level Vision: **comprehension** (what
298
+ is this, what could you not resolve), **gate operability** (the battery above),
299
+ **durability** (what rots, what is unfalsifiable, what is a time bomb). For a
300
+ feature-level Vision or an M-VISION, the gate lens alone is usually enough.
301
+
302
+ ## 7. Cost, honestly
303
+
304
+ A first draft written against §1–§4 will still have findings — the checklist
305
+ removes the structural classes, not judgement errors. Expect one blind round to
306
+ find real things and a second to confirm. What it should NOT take is five rounds
307
+ of discovering the same class of defect in a new disguise; that is the specific
308
+ waste this file exists to prevent.