harness-everything 0.30.5 → 0.30.7

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.
Files changed (77) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +44 -0
  4. package/ci/fable-mode-test.js +112 -51
  5. package/ci/mechanism-2ad-action-gate-classifier-hardening.test.js +14 -0
  6. package/ci/mechanism-2t-workspace-identity.test.js +3 -1
  7. package/ci/mechanism-2z-claude-plugin-manifest.test.js +12 -0
  8. package/ci/prompt-contract-regressions.test.js +37 -0
  9. package/ci/router-workflow-plan.test.js +92 -7
  10. package/docs/platform-capabilities.md +8 -0
  11. package/docs/workflows/fable-mode.md +11 -10
  12. package/evals/fable-mode/tasks/negative-tier2-no-model.yaml +3 -3
  13. package/evals/fable-mode/tasks/positive-haiku-mode.yaml +1 -1
  14. package/evals/fable-mode/tasks/positive-opus-mode.yaml +1 -1
  15. package/evals/fable-mode/tasks/positive-sonnet-mode.yaml +1 -1
  16. package/evals/fable-mode/tasks/positive-trigger.yaml +1 -1
  17. package/fable-mode/SKILL.md +19 -13
  18. package/fable-mode/agents/fable-orchestrator.md +7 -6
  19. package/fable-mode/agents/fable-verifier.md +1 -1
  20. package/fable-mode/agents/fable-worker-haiku.md +5 -4
  21. package/fable-mode/agents/fable-worker-sonnet.md +5 -4
  22. package/fable-mode/behavior-profile-matrix.json +36 -0
  23. package/fable-mode/execution-guardrails/SKILL.md +3 -6
  24. package/fable-mode/execution-guardrails/references/guardrail-rules.md +8 -9
  25. package/fable-mode/fable-haiku/SKILL.md +7 -5
  26. package/fable-mode/fable-haiku/references/routing-notes.md +2 -1
  27. package/fable-mode/fable-opus/SKILL.md +9 -7
  28. package/fable-mode/fable-sonnet/SKILL.md +8 -6
  29. package/fable-mode/model-matrix.json +7 -30
  30. package/fable-mode/references/execution-phases.md +2 -1
  31. package/fable-mode/references/model-matrix.md +35 -30
  32. package/fable-mode/references/profile-lookup.md +19 -0
  33. package/fable-mode/runtime-model-floor-matrix.json +47 -0
  34. package/fable-mode/scripts/model-selector.js +106 -60
  35. package/fable-mode/scripts/workflow-plan-consumer.js +1 -1
  36. package/find-skills/SKILL.md +2 -2
  37. package/find-skills/references/discovery-flow.md +1 -1
  38. package/harness-everything/SKILL.md +1 -1
  39. package/harness-everything/references/router-workflow-plan.md +2 -2
  40. package/harness-everything/schemas/router-task-shape.schema.json +3 -2
  41. package/harness-everything/schemas/router-workflow-plan.schema.json +6 -5
  42. package/harness-everything/scripts/router-contract-core.js +19 -12
  43. package/harness-everything/scripts/tier-router.js +100 -12
  44. package/hooks/scripts/action-gate.js +11 -0
  45. package/opencode-plugin/plugin.json +1 -1
  46. package/package.json +1 -1
  47. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  48. package/plugins/harness-everything/hooks/scripts/action-gate.js +11 -0
  49. package/plugins/harness-everything/plugin.json +1 -1
  50. package/plugins/harness-everything/skills/fable-mode/SKILL.md +19 -13
  51. package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +7 -6
  52. package/plugins/harness-everything/skills/fable-mode/agents/fable-verifier.md +1 -1
  53. package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-haiku.md +5 -4
  54. package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-sonnet.md +5 -4
  55. package/plugins/harness-everything/skills/fable-mode/behavior-profile-matrix.json +36 -0
  56. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +3 -6
  57. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/references/guardrail-rules.md +8 -9
  58. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +7 -5
  59. package/plugins/harness-everything/skills/fable-mode/fable-haiku/references/routing-notes.md +2 -1
  60. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +9 -7
  61. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +8 -6
  62. package/plugins/harness-everything/skills/fable-mode/model-matrix.json +7 -30
  63. package/plugins/harness-everything/skills/fable-mode/references/execution-phases.md +2 -1
  64. package/plugins/harness-everything/skills/fable-mode/references/model-matrix.md +35 -30
  65. package/plugins/harness-everything/skills/fable-mode/references/profile-lookup.md +19 -0
  66. package/plugins/harness-everything/skills/fable-mode/runtime-model-floor-matrix.json +47 -0
  67. package/plugins/harness-everything/skills/fable-mode/scripts/model-selector.js +106 -60
  68. package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +1 -1
  69. package/plugins/harness-everything/skills/find-skills/SKILL.md +2 -2
  70. package/plugins/harness-everything/skills/find-skills/references/discovery-flow.md +1 -1
  71. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  72. package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +2 -2
  73. package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +3 -2
  74. package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +6 -5
  75. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +19 -12
  76. package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +100 -12
  77. package/sbom.cdx.json +7 -7
@@ -10,7 +10,7 @@
10
10
  "name": "harness-everything",
11
11
  "source": "./",
12
12
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
13
- "version": "0.30.5",
13
+ "version": "0.30.7",
14
14
  "author": {
15
15
  "name": "Miya Daniel"
16
16
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-everything",
3
3
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
4
- "version": "0.30.5",
4
+ "version": "0.30.7",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.30.7](https://github.com/dyphn1/Harness-everything/compare/v0.30.6...v0.30.7) (2026-10-07)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **action-gate:** reject dynamic scratch-target exemptions ([#283](https://github.com/dyphn1/Harness-everything/issues/283)) ([f722968](https://github.com/dyphn1/Harness-everything/commit/f722968021846a804fa8fceaddd9983addff51e7))
10
+ * **action-gate:** use explicit shell-expansion marker check ([f02a105](https://github.com/dyphn1/Harness-everything/commit/f02a105e007104d20cde8fe46d24668624f7b343))
11
+ * **action-gate:** use explicit shell-expansion marker check ([bedf24b](https://github.com/dyphn1/Harness-everything/commit/bedf24b1d2774e1d997f4b5c3f1f5d3c90694311))
12
+ * **fable:** keep advisory replan contract within skill token budget ([b593c49](https://github.com/dyphn1/Harness-everything/commit/b593c49862d779f20919f8f1a7ff72043d76b637))
13
+ * **fable:** keep advisory replan contract within skill token budget ([da6c5e8](https://github.com/dyphn1/Harness-everything/commit/da6c5e8f238d5dcae9632a5db0e71786d7dc5fe7))
14
+ * **fable:** make replan guidance non-blocking ([#273](https://github.com/dyphn1/Harness-everything/issues/273)) ([64de668](https://github.com/dyphn1/Harness-everything/commit/64de668e91300e1c7f68ca07ebb08d87b5595c1c))
15
+ * **fable:** make replan guidance non-blocking ([#273](https://github.com/dyphn1/Harness-everything/issues/273)) ([241cf46](https://github.com/dyphn1/Harness-everything/commit/241cf461baca6a6e739aa5114330ac1e20c0195b))
16
+ * **fable:** make replan guidance non-blocking ([#273](https://github.com/dyphn1/Harness-everything/issues/273)) ([d834d9f](https://github.com/dyphn1/Harness-everything/commit/d834d9fffbae5d40583c1e0e41a1da1f03531382))
17
+ * **fable:** make replan guidance non-blocking ([#273](https://github.com/dyphn1/Harness-everything/issues/273)) ([1430afe](https://github.com/dyphn1/Harness-everything/commit/1430afe9a96a1b849d18982644193bf7251264b1))
18
+ * **fable:** preserve per-stage artifact and pass-check contract ([2c4feaf](https://github.com/dyphn1/Harness-everything/commit/2c4feafd503881296a8b656d2b6c8257db68be41))
19
+ * **fable:** preserve per-stage artifact and pass-check contract ([d88a0b6](https://github.com/dyphn1/Harness-everything/commit/d88a0b6b054ca0810563519eed2a1d494c8b3e15))
20
+ * **fable:** remove remaining two-replan caps from fable references ([63f9263](https://github.com/dyphn1/Harness-everything/commit/63f9263f3dd712c39ec49aa977e0229dc750bb52))
21
+ * **find-skills:** retain authority boundary in reference contract ([#276](https://github.com/dyphn1/Harness-everything/issues/276)) ([9a5ed99](https://github.com/dyphn1/Harness-everything/commit/9a5ed99737be4fe0b8631370379eead2d54f6bf9))
22
+ * **find-skills:** retain authority boundary in reference contract ([#276](https://github.com/dyphn1/Harness-everything/issues/276)) ([3e41033](https://github.com/dyphn1/Harness-everything/commit/3e410339df82dafccd2cef094b00e4efd7f4e2d7))
23
+ * **find-skills:** retain authority boundary in skill contract ([#276](https://github.com/dyphn1/Harness-everything/issues/276)) ([3d01cd7](https://github.com/dyphn1/Harness-everything/commit/3d01cd7633261851529b214dbc01664b2a18a915))
24
+ * **find-skills:** retain authority boundary in skill contract ([#276](https://github.com/dyphn1/Harness-everything/issues/276)) ([2112e3a](https://github.com/dyphn1/Harness-everything/commit/2112e3a04eb3c90786aa3316d2735d24435fc9f0))
25
+
26
+ ## [0.30.6](https://github.com/dyphn1/Harness-everything/compare/v0.30.5...v0.30.6) (2026-10-06)
27
+
28
+ ### Bug Fixes
29
+
30
+ * **fable-mode:** align Claude nested skill contract ([aadfe34](https://github.com/dyphn1/Harness-everything/commit/aadfe349e6f9a6ce9623f9b408ad8c6d9958208e))
31
+ * **fable-mode:** compress guardrail reference contract ([302efcd](https://github.com/dyphn1/Harness-everything/commit/302efcd0433c75be10619dbad7aa2b925b57400f))
32
+ * **fable-mode:** preserve unknown audit states and raw aliases ([2716be9](https://github.com/dyphn1/Harness-everything/commit/2716be9feb3e6b986c0f5da884ed4a686b95791b))
33
+ * **fable-mode:** remove residual model-gated routing ([32fee42](https://github.com/dyphn1/Harness-everything/commit/32fee423e9af7416172bf146cb876f72d61e331e))
34
+ * **fable-mode:** stay within skill token budget ([d934d36](https://github.com/dyphn1/Harness-everything/commit/d934d36cef17d5e0b30dfffd888cdab5d7c9a747))
35
+ * **fable:** accept curly-quoted lookups and restore execution deep dive ([08517e4](https://github.com/dyphn1/Harness-everything/commit/08517e4469bab46042358df136446e2495144a7f)), closes [#290](https://github.com/dyphn1/Harness-everything/issues/290)
36
+ * **fable:** dispatch profile lookups before loading stage references ([58ce883](https://github.com/dyphn1/Harness-everything/commit/58ce8834d3fa0ab288f9106f26758d4945ca49b6)), closes [#290](https://github.com/dyphn1/Harness-everything/issues/290)
37
+ * **router:** preserve Fable profile alias across normalization ([50e84da](https://github.com/dyphn1/Harness-everything/commit/50e84da3f14216800e4c757996663debfb7a8f7a))
38
+ * **router:** scope fable lookup negations to their own list ([032b52b](https://github.com/dyphn1/Harness-everything/commit/032b52bba809237c4b891e44fc7ee7b082262ddb)), closes [#290](https://github.com/dyphn1/Harness-everything/issues/290)
39
+ * **router:** select bounded Fable profile lookups before staged topology ([0d6fdac](https://github.com/dyphn1/Harness-everything/commit/0d6fdac675a9225f9425fcb699444992032371a3)), closes [#290](https://github.com/dyphn1/Harness-everything/issues/290)
40
+
41
+ ## [Unreleased]
42
+
43
+ ### Fixed
44
+
45
+ - Select a bounded Fable profile lookup before loading staged workflow references; preserve execution topology and required obligations (#290).
46
+ - Validate entire Fable lookup clauses so same-clause execution keeps its obligations, while canonical JSON field lists remain bounded lookups (#294).
47
+ - Scope lookup negations to their own noun list so an un-negated action after a negation keeps execution routing (#294).
48
+
5
49
  ## [0.30.5](https://github.com/dyphn1/Harness-everything/compare/v0.30.4...v0.30.5) (2026-10-06)
6
50
 
7
51
  ### Bug Fixes
@@ -5,6 +5,7 @@ const fs = require('fs');
5
5
  const os = require('os');
6
6
  const { spawnSync } = require('child_process');
7
7
  const path = require('path');
8
+ const { evaluateRuntimeFloor } = require('../fable-mode/scripts/model-selector.js');
8
9
 
9
10
  const root = path.resolve(__dirname, '..');
10
11
  const selector = path.join(root, 'fable-mode', 'scripts', 'model-selector.js');
@@ -13,19 +14,17 @@ const auditDir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-fable-mode-'));
13
14
  const auditFile = path.join(auditDir, 'audit.jsonl');
14
15
 
15
16
  function runNode(file, args) {
16
- return spawnSync(process.execPath, [file, ...args], {
17
- cwd: root,
18
- encoding: 'utf8'
19
- });
17
+ return spawnSync(process.execPath, [file, ...args], { cwd: root, encoding: 'utf8' });
20
18
  }
21
19
 
22
20
  function selectorArgs(overrides = {}) {
23
21
  const input = {
24
22
  requested: 'opus',
25
- available: 'opus,sonnet,haiku',
26
23
  'available-agents': 'fable-orchestrator,fable-worker-sonnet,fable-worker-haiku',
27
24
  fallback: 'stop',
28
- 'host-model': 'opus',
25
+ host: 'codex',
26
+ 'runtime-model': 'gpt-6.1-sol',
27
+ 'runtime-effort': 'xhigh',
29
28
  'audit-file': auditFile,
30
29
  'stage-brief': 'Choose the architecture',
31
30
  'pass-condition': 'ADR exists and names the decision',
@@ -33,104 +32,166 @@ function selectorArgs(overrides = {}) {
33
32
  'verifier-result': 'pending',
34
33
  ...overrides
35
34
  };
36
- return Object.entries(input).flatMap(([key, value]) => [`--${key}`, value]);
35
+ return Object.entries(input).flatMap(([key, value]) => ['--' + key, String(value)]);
37
36
  }
38
37
 
39
38
  function readRecord(result) {
40
- assert.ok(result.stdout.trim(), `selector produced no JSON: ${result.stderr}`);
39
+ assert.ok(result.stdout.trim(), 'selector produced no JSON: ' + result.stderr);
41
40
  return JSON.parse(result.stdout);
42
41
  }
43
42
 
44
- console.log('=== Fable model mode contract tests ===');
43
+ console.log('=== Fable behavior profile + runtime floor contract tests ===');
44
+
45
+ const publicDescription = 'Stage large tasks through plans, named fable agents, failable checks, and skeptical review; fable on opus|sonnet|haiku selects a behavior profile, not a runtime model.';
46
+ const fableSkillText = fs.readFileSync(path.join(root, 'fable-mode', 'SKILL.md'), 'utf8');
47
+ assert.ok(fableSkillText.includes('description: "' + publicDescription + '"'));
48
+
49
+ for (const task of ['positive-opus-mode.yaml', 'positive-sonnet-mode.yaml', 'positive-haiku-mode.yaml', 'positive-trigger.yaml']) {
50
+ const source = fs.readFileSync(path.join(root, 'evals', 'fable-mode', 'tasks', task), 'utf8');
51
+ assert.ok(source.includes('description: "' + publicDescription + '"'), task + ' must keep the exact public fable-mode description');
52
+ }
53
+
54
+ const behavior = JSON.parse(fs.readFileSync(path.join(root, 'fable-mode', 'behavior-profile-matrix.json'), 'utf8'));
55
+ assert.strictEqual(behavior.profiles.orchestrator.legacyAlias, 'opus');
56
+ assert.ok(behavior.profiles.reasoning.aliases.includes('sonnect'));
57
+
58
+ const floors = JSON.parse(fs.readFileSync(path.join(root, 'fable-mode', 'runtime-model-floor-matrix.json'), 'utf8'));
59
+ assert.strictEqual(floors.hosts.claude.orchestrator.minVersion, '5.5');
60
+ assert.strictEqual(floors.hosts.claude.orchestrator.minEffort, 'medium');
61
+ assert.strictEqual(floors.hosts.claude.reasoning.minVersion, '5.5');
62
+ assert.strictEqual(floors.hosts.claude.mechanical.minVersion, '5.5');
63
+ assert.strictEqual(floors.hosts.codex.orchestrator.minGeneration, 6);
64
+ assert.strictEqual(floors.hosts.codex.orchestrator.minEffort, 'xhigh');
65
+ assert.strictEqual(floors.hosts.codex.reasoning.minEffort, 'medium');
66
+ assert.strictEqual(floors.hosts.codex.mechanical.family, 'luna');
67
+ assert.strictEqual(floors.hosts.codex.mechanical.minEffort, 'xhigh');
68
+
69
+ for (const profile of ['fable-opus', 'fable-sonnet', 'fable-haiku']) {
70
+ const source = fs.readFileSync(path.join(root, 'fable-mode', profile, 'SKILL.md'), 'utf8');
71
+ assert.match(source, /Reference profile, not a standalone plugin skill/);
72
+ }
73
+ for (const agent of ['fable-orchestrator.md', 'fable-worker-sonnet.md', 'fable-worker-haiku.md', 'fable-verifier.md']) {
74
+ const source = fs.readFileSync(path.join(root, 'fable-mode', 'agents', agent), 'utf8');
75
+ assert.match(source, /^model: inherit$/m, agent + ' must not pin a branded runtime model');
76
+ }
77
+
78
+ const guardrailSource = fs.readFileSync(path.join(root, 'fable-mode', 'execution-guardrails', 'SKILL.md'), 'utf8');
79
+ assert.match(guardrailSource, /reference-only, not an always-on carrier/);
80
+ assert.doesNotMatch(guardrailSource, /These rules are always-on/);
45
81
 
46
82
  const selected = runNode(selector, selectorArgs());
47
83
  assert.strictEqual(selected.status, 0, selected.stderr);
48
84
  const selectedRecord = readRecord(selected);
49
85
  assert.strictEqual(selectedRecord.status, 'selected');
50
- assert.strictEqual(selectedRecord.requestedModel, 'opus');
51
- assert.strictEqual(selectedRecord.effectiveModel, 'opus');
52
- assert.strictEqual(selectedRecord.fallbackReason, 'none');
86
+ assert.strictEqual(selectedRecord.requestedProfile, 'orchestrator');
87
+ assert.strictEqual(selectedRecord.effectiveProfile, 'orchestrator');
88
+ assert.strictEqual(selectedRecord.profileAlias, 'opus');
53
89
  assert.strictEqual(selectedRecord.agent, 'fable-orchestrator');
90
+ assert.strictEqual(selectedRecord.runtimeFloorStatus, 'meets-recommended');
54
91
 
55
- const sonnect = runNode(selector, selectorArgs({ requested: 'sonnect', available: 'haiku,sonnet' }));
92
+ const sonnect = runNode(selector, selectorArgs({
93
+ requested: 'sonnect',
94
+ host: 'claude',
95
+ 'runtime-model': 'claude-sonnet-5.5',
96
+ 'runtime-effort': 'medium'
97
+ }));
56
98
  assert.strictEqual(sonnect.status, 0, sonnect.stderr);
57
99
  const sonnectRecord = readRecord(sonnect);
58
- assert.strictEqual(sonnectRecord.requestedModel, 'sonnet');
59
- assert.strictEqual(sonnectRecord.requestedInput, 'sonnect');
100
+ assert.strictEqual(sonnectRecord.requestedProfile, 'reasoning');
101
+ assert.strictEqual(sonnectRecord.profileAlias, 'sonnect');
102
+ assert.strictEqual(sonnectRecord.runtimeFloorStatus, 'meets-recommended');
60
103
 
61
- const inline = runNode(selector, selectorArgs({
104
+ const belowFloor = runNode(selector, selectorArgs({
62
105
  requested: 'opus',
63
- available: 'sonnet',
64
- fallback: 'inline',
65
- 'host-model': 'sonnet'
106
+ host: 'codex',
107
+ 'runtime-model': 'gpt-6-sol',
108
+ 'runtime-effort': 'medium'
109
+ }));
110
+ assert.strictEqual(belowFloor.status, 0, belowFloor.stderr);
111
+ const belowFloorRecord = readRecord(belowFloor);
112
+ assert.strictEqual(belowFloorRecord.status, 'selected');
113
+ assert.strictEqual(belowFloorRecord.effectiveProfile, 'orchestrator');
114
+ assert.strictEqual(belowFloorRecord.runtimeFloorStatus, 'below-recommended');
115
+ assert.strictEqual(belowFloorRecord.escalationRequired, false);
116
+
117
+ const unknownFamily = evaluateRuntimeFloor('reasoning', {
118
+ host: 'codex',
119
+ runtimeModel: 'vendor-private-model',
120
+ runtimeEffort: 'xhigh'
121
+ });
122
+ assert.strictEqual(unknownFamily.status, 'unknown');
123
+ assert.match(unknownFamily.reason, /not comparable/);
124
+
125
+ const unknownEffort = evaluateRuntimeFloor('reasoning', {
126
+ host: 'codex',
127
+ runtimeModel: 'gpt-6.1-sol',
128
+ runtimeEffort: 'custom-effort'
129
+ });
130
+ assert.strictEqual(unknownEffort.status, 'unknown');
131
+ assert.match(unknownEffort.reason, /not comparable/);
132
+
133
+ const inline = runNode(selector, selectorArgs({
134
+ requested: 'sonnet',
135
+ 'available-agents': 'fable-worker-haiku',
136
+ fallback: 'inline'
66
137
  }));
67
138
  assert.strictEqual(inline.status, 0, inline.stderr);
68
139
  const inlineRecord = readRecord(inline);
69
140
  assert.strictEqual(inlineRecord.status, 'fallback');
70
- assert.strictEqual(inlineRecord.effectiveModel, 'sonnet');
71
- assert.match(inlineRecord.fallbackReason, /opus is not in availableModels/);
72
- assert.strictEqual(inlineRecord.escalationRequired, false);
141
+ assert.strictEqual(inlineRecord.effectiveProfile, 'reasoning');
142
+ assert.match(inlineRecord.fallbackReason, /execute the same reasoning behavior profile inline/);
73
143
 
74
- const blocked = runNode(selector, selectorArgs({ requested: 'opus', available: 'sonnet' }));
144
+ const blocked = runNode(selector, selectorArgs({
145
+ requested: 'sonnet',
146
+ 'available-agents': 'fable-worker-haiku',
147
+ fallback: 'stop'
148
+ }));
75
149
  assert.strictEqual(blocked.status, 2);
76
150
  const blockedRecord = readRecord(blocked);
77
151
  assert.strictEqual(blockedRecord.status, 'blocked');
78
- assert.strictEqual(blockedRecord.effectiveModel, null);
152
+ assert.strictEqual(blockedRecord.effectiveProfile, null);
79
153
  assert.strictEqual(blockedRecord.escalationRequired, true);
80
154
  assert.match(blockedRecord.fallbackReason, /inline fallback was not authorized/);
81
155
 
82
- const missingAgent = runNode(selector, selectorArgs({
83
- requested: 'sonnet',
84
- available: 'sonnet',
85
- 'available-agents': 'fable-worker-haiku',
86
- fallback: 'inline',
87
- 'host-model': 'opus'
88
- }));
89
- assert.strictEqual(missingAgent.status, 0, missingAgent.stderr);
90
- const missingAgentRecord = readRecord(missingAgent);
91
- assert.strictEqual(missingAgentRecord.status, 'fallback');
92
- assert.strictEqual(missingAgentRecord.effectiveModel, 'opus');
93
- assert.match(missingAgentRecord.fallbackReason, /fable-worker-sonnet is not in availableAgents/);
94
-
95
156
  const missingField = runNode(selector, selectorArgs({ 'pass-condition': '' }));
96
157
  assert.strictEqual(missingField.status, 2);
97
158
  assert.match(missingField.stderr, /passCondition is required/);
98
159
 
99
160
  const explicitRoute = runNode(router, ['fable on sonnect for architecture synthesis']);
100
161
  assert.strictEqual(explicitRoute.status, 0);
101
- assert.match(explicitRoute.stdout, /REQUESTED FABLE MODEL MODE: sonnet/);
102
- assert.match(explicitRoute.stdout, /fable-mode\/fable-sonnet\/SKILL\.md/);
162
+ assert.match(explicitRoute.stdout, /REQUESTED FABLE PROFILE: sonnect/);
163
+ assert.match(explicitRoute.stdout, /=> ROUTE: fable-mode\/SKILL\.md/);
164
+ assert.doesNotMatch(explicitRoute.stdout, /REQUESTED FABLE MODEL MODE/);
165
+ assert.doesNotMatch(explicitRoute.stdout, /=> ROUTE: fable-mode\/fable-sonnet\/SKILL\.md/);
103
166
 
104
167
  const ordinaryTier2 = runNode(router, ['Fix this checkout bug and add a regression test.']);
105
168
  assert.strictEqual(ordinaryTier2.status, 0);
106
169
  assert.match(ordinaryTier2.stdout, /RECOMMENDED TIER: Tier 2/);
107
- assert.doesNotMatch(ordinaryTier2.stdout, /REQUESTED FABLE MODEL MODE/);
108
- assert.doesNotMatch(ordinaryTier2.stdout, /fable-opus\/SKILL\.md/);
170
+ assert.doesNotMatch(ordinaryTier2.stdout, /REQUESTED FABLE PROFILE/);
109
171
 
110
172
  const warningBatchContract = '**Warning batching.** Collect minor concerns and list them together in the next natural report or handoff; their count alone MUST NOT stop, pause, or return the worker/stage. An independently material, confirmed concern may stop the current stage and be surfaced immediately.';
111
173
  for (const rel of [
112
174
  'fable-mode/agents/fable-worker-haiku.md',
113
175
  'fable-mode/agents/fable-worker-sonnet.md',
114
176
  'fable-mode/agents/fable-orchestrator.md',
115
- 'fable-mode/execution-guardrails/SKILL.md',
177
+ 'fable-mode/execution-guardrails/SKILL.md'
116
178
  ]) {
117
179
  const prompt = fs.readFileSync(path.join(root, rel), 'utf8').replace(/\s+/g, ' ');
118
- assert.ok(prompt.includes(warningBatchContract), `${rel} must keep minor-concern batching reporting-only`);
119
- assert.doesNotMatch(
120
- prompt,
121
- /minor concerns.{0,200}stop and surface all at once before continuing/i,
122
- `${rel} must not introduce a warning-count stop condition`
123
- );
180
+ assert.ok(prompt.includes(warningBatchContract), rel + ' must keep minor-concern batching reporting-only');
124
181
  }
125
182
 
126
183
  const auditLines = fs.readFileSync(auditFile, 'utf8').trim().split(/\r?\n/).filter(Boolean);
127
184
  assert.strictEqual(auditLines.length, 5);
128
185
  for (const line of auditLines) {
129
186
  const record = JSON.parse(line);
130
- for (const field of ['requestedModel', 'effectiveModel', 'fallbackReason', 'stageBrief', 'passCondition', 'verificationCommand', 'verifierResult']) {
131
- assert.ok(Object.prototype.hasOwnProperty.call(record, field), `audit missing ${field}`);
187
+ for (const field of [
188
+ 'requestedProfile', 'effectiveProfile', 'profileAlias', 'runtimeModel', 'runtimeEffort',
189
+ 'recommendedRuntimeFloor', 'runtimeFloorStatus', 'runtimeFloorReason', 'fallbackReason',
190
+ 'stageBrief', 'passCondition', 'verificationCommand', 'verifierResult'
191
+ ]) {
192
+ assert.ok(Object.prototype.hasOwnProperty.call(record, field), 'audit missing ' + field);
132
193
  }
133
194
  }
134
195
  fs.rmSync(auditDir, { recursive: true, force: true });
135
196
 
136
- console.log('PASS: selected, sonnect alias, inline fallback, blocked escalation, audit fields, and routing separation');
197
+ console.log('PASS: profiles are behavior contracts; runtime floors are advisory; agent fallback remains explicit');
@@ -84,6 +84,20 @@ try {
84
84
  // fail-open path: the gate cannot prove that the target is scratch space.
85
85
  matched(table, 'rm -rf "$S"', 'recursive-delete');
86
86
 
87
+ // A repository checkout may itself live below os.tmpdir() (e.g. a review worktree).
88
+ // Unknown paths are not proven scratch paths merely because cwd is scratch.
89
+ const scratchCwd = path.join(sessionDir, 'scratch');
90
+ matched(table, 'rm -rf "$S"', 'recursive-delete', 'Bash', scratchCwd);
91
+ matched(table, 'rm -rf "${S}"', 'recursive-delete', 'Bash', scratchCwd);
92
+ matched(table, 'rm -rf "$HOME/important"', 'recursive-delete', 'Bash', scratchCwd);
93
+ matched(table, 'rm -rf "~/important"', 'recursive-delete', 'Bash', scratchCwd);
94
+ matched(table, 'rm -rf "*.txt"', 'recursive-delete', 'Bash', scratchCwd);
95
+ matched(table, 'ri -Recurse -Force "$env:TARGET"', 'powershell-recursive-force-delete', 'PowerShell', scratchCwd);
96
+ matched(table, 'rm -r -fo "%TEMP%/important"', 'powershell-recursive-force-delete', 'PowerShell', scratchCwd);
97
+ // A provably literal scratch path remains allowed even from the same cwd.
98
+ allowed(table, `rm -rf "${path.join(scratchCwd, 'output')}"`, 'Bash', scratchCwd);
99
+
100
+
87
101
  console.log('PASS: destructive-command classifier covers confirmed bypasses while suppressing quoted-text false positives');
88
102
  } finally {
89
103
  fs.rmSync(temp, { recursive: true, force: true });
@@ -131,7 +131,9 @@ check('hooks can resolve state from a bound session after cwd changes', result.s
131
131
  const installed = path.join(fixture, 'installed', 'fable-mode', 'scripts');
132
132
  fs.mkdirSync(installed, { recursive: true });
133
133
  fs.copyFileSync(path.join(root, 'fable-mode', 'scripts', 'model-selector.js'), path.join(installed, 'model-selector.js'));
134
- fs.copyFileSync(path.join(root, 'fable-mode', 'model-matrix.json'), path.join(installed, '..', 'model-matrix.json'));
134
+ for (const matrix of ['behavior-profile-matrix.json', 'runtime-model-floor-matrix.json']) {
135
+ fs.copyFileSync(path.join(root, 'fable-mode', matrix), path.join(installed, '..', matrix));
136
+ }
135
137
  const fable = path.join(installed, 'model-selector.js');
136
138
  const fableArgs = ['--requested', 'haiku', '--available', 'haiku', '--available-agents', 'fable-worker-haiku', '--stage-brief', 'identity', '--pass-condition', 'audit', '--verification-command', 'node test', '--verifier-result', 'pass', '--session-id', 'issue-42-fable-session'];
137
139
  result = spawnSync(process.execPath, [fable, ...fableArgs], { cwd: outer, env: baseEnv, encoding: 'utf8' });
@@ -81,6 +81,18 @@ if (manifestHooks !== undefined) {
81
81
  }
82
82
 
83
83
  assert.ok(Array.isArray(manifest.skills) && manifest.skills.length > 0, 'Claude plugin must declare packaged skills');
84
+ assert.ok(manifest.skills.includes('./fable-mode'), 'Claude plugin must expose fable-mode as the public Fable entrypoint');
85
+ for (const nestedRef of [
86
+ './fable-mode/fable-opus',
87
+ './fable-mode/fable-sonnet',
88
+ './fable-mode/fable-haiku',
89
+ './fable-mode/execution-guardrails',
90
+ ]) {
91
+ assert.ok(
92
+ !manifest.skills.includes(nestedRef),
93
+ `${nestedRef} is a nested reference contract, not an independently routable Claude skill`
94
+ );
95
+ }
84
96
  for (const skillRef of manifest.skills) {
85
97
  const skillDir = resolvePluginPath(skillRef, `skill ${skillRef}`);
86
98
  assert.ok(fs.statSync(skillDir).isDirectory(), `skill path must be a directory: ${skillRef}`);
@@ -10,6 +10,29 @@ function read(rel) {
10
10
  return fs.readFileSync(path.join(ROOT, rel), 'utf8');
11
11
  }
12
12
 
13
+ // #273: Fable replan counts are advisory, never execution hard-stops.
14
+ // Only repeated same-signature failures invoke mandatory Rule-of-3 reflection.
15
+ for (const [canonical, packaged] of [
16
+ ['fable-mode/SKILL.md', 'plugins/harness-everything/skills/fable-mode/SKILL.md'],
17
+ ['fable-mode/fable-opus/SKILL.md', 'plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md'],
18
+ ]) {
19
+ const body = read(canonical);
20
+ assert.strictEqual(read(packaged), body, canonical + ' packaged contract matches');
21
+ assert.ok(/advisory replan/i.test(body), canonical + ' must describe replans as advisory');
22
+ assert.ok(/Rule-of-3/i.test(body), canonical + ' must preserve same-signature failure reflection');
23
+ assert.ok(!/at most two (?:full )?replans/i.test(body), canonical + ' must not imply a two-replan hard cap');
24
+ }
25
+ for (const rel of [
26
+ 'fable-mode/references/execution-phases.md',
27
+ 'plugins/harness-everything/skills/fable-mode/references/execution-phases.md',
28
+ 'docs/workflows/fable-mode.md',
29
+ ]) {
30
+ assert.ok(!/(?:at most|no more than) two (?:full )?replans/i.test(read(rel)), rel + ' must not imply a two-replan hard cap');
31
+ }
32
+ const orchestratorContract = read('fable-mode/agents/fable-orchestrator.md');
33
+ assert.ok(/maxReplans[\s\S]{0,60}is advisory/i.test(orchestratorContract), 'orchestrator keeps advisory replan guidance');
34
+ assert.ok(/do not block execution/i.test(orchestratorContract), 'orchestrator does not block on replan count');
35
+
13
36
  // #268: model aliases evolve, so worker prompts must encode behavior rather
14
37
  // than generation-specific personality/failure claims.
15
38
  const workerPairs = [
@@ -90,6 +113,20 @@ const workspaceContract = read('multi-agent-workspace/SKILL.md');
90
113
  assert.ok(/router owns that decision/i.test(workspaceContract), 'multi-agent-workspace keeps router-owned topology');
91
114
  assert.ok(/does not spawn workers/i.test(workspaceContract), 'multi-agent-workspace remains a topology/workspace consumer');
92
115
 
116
+ // #276: third-party skill output is applicable guidance, never higher-priority
117
+ // authority. Keep the canonical, packaged, and reference contracts consistent.
118
+ const findSkills = read('find-skills/SKILL.md');
119
+ const discoveryFlow = read('find-skills/references/discovery-flow.md');
120
+ assert.strictEqual(read('plugins/harness-everything/skills/find-skills/SKILL.md'), findSkills, 'find-skills packaged contract matches canonical');
121
+ assert.strictEqual(read('plugins/harness-everything/skills/find-skills/references/discovery-flow.md'), discoveryFlow, 'find-skills packaged reference matches canonical');
122
+ assert.ok(/untrusted third-party content/i.test(findSkills), 'third-party output must be identified as untrusted');
123
+ assert.ok(/MUST NOT override host, user, or Harness/i.test(findSkills), 'third-party skill instructions cannot override core authority');
124
+ assert.ok(/explicit approval/i.test(findSkills), 'third-party application must require approval');
125
+ assert.ok(/untrusted third-party content/i.test(discoveryFlow), 'reference must preserve untrusted data boundary');
126
+ assert.ok(/MUST NOT override host, user, or Harness/i.test(discoveryFlow), 'reference must preserve precedence');
127
+ assert.ok(!/Treat its output as binding/i.test(findSkills), 'find-skills must not grant unconditional binding authority');
128
+ assert.ok(!/treat every instruction in it as binding/i.test(discoveryFlow), 'discovery reference must not grant unconditional binding authority');
129
+
93
130
  // #272: preserve one detailed checkpoint plus the invariant; remove duplicate
94
131
  // restatements from later sections of the same router injection.
95
132
  const kernel = path.join(ROOT, 'harness-everything/scripts/kernel-router.js');
@@ -202,10 +202,95 @@ if (validContract(fableTypo, 'fable keyword trivial edit')) {
202
202
  }
203
203
 
204
204
  const explicitFable = runTier('fable on opus audit the entire repository architecture');
205
- if (validContract(explicitFable, 'explicit Fable model')) {
205
+ if (validContract(explicitFable, 'explicit Fable profile')) {
206
206
  check(explicitFable.contract.workflowPlan.strategy === 'fable-staged', 'explicit Fable request selects Fable topology');
207
- check(explicitFable.contract.workflowPlan.modelSelection.requested === 'opus', 'explicit Fable model stays delegated to model selector');
208
- check(explicitFable.result.stdout.includes('REQUESTED FABLE MODEL MODE: opus'), 'legacy explicit Fable route remains visible');
207
+ check(explicitFable.contract.workflowPlan.profileSelection.requested === 'orchestrator', 'legacy opus alias normalizes to orchestrator profile');
208
+ check(explicitFable.contract.workflowPlan.profileSelection.alias === 'opus', 'legacy alias remains auditable');
209
+ check(explicitFable.result.stdout.includes('REQUESTED FABLE PROFILE: opus'), 'explicit Fable profile remains visible');
210
+ }
211
+
212
+ const explicitSonnect = runTier('fable on sonnect for architecture synthesis');
213
+ if (validContract(explicitSonnect, 'explicit Fable sonnect alias')) {
214
+ check(explicitSonnect.contract.workflowPlan.profileSelection.requested === 'reasoning', 'sonnect normalizes to reasoning profile');
215
+ check(explicitSonnect.contract.workflowPlan.profileSelection.alias === 'sonnect', 'raw sonnect alias remains auditable end-to-end');
216
+ check(explicitSonnect.result.stdout.includes('REQUESTED FABLE PROFILE: sonnect'), 'router output preserves raw sonnect alias');
217
+ }
218
+
219
+ // #290: mentioning a profile in a bounded lookup must not launch orchestration.
220
+ for (const prompt of [
221
+ "Explain what 'fable on sonnect' means; return only a small JSON selection record.",
222
+ "Resolve fable on haiku into a profile JSON record; no orchestration or delegation.",
223
+ "解釋 fable on opus 的意思,只回傳 JSON。",
224
+ "Explain what 'run fable on sonnect' means; return only JSON.",
225
+ 'Explain fable on sonnet; return JSON with requestedProfile, effectiveProfile.',
226
+ "Explain what 'fable on sonnect' means; return only a small JSON selection record with requestedProfile, effectiveProfile, profileAlias, assignedRole, runtimeModel, runtimeEffort. No orchestration or delegation.",
227
+ 'Explain fable on haiku; output JSON with `requestedProfile`, `runtimeModel` and `runtimeEffort`.',
228
+ '解釋 fable on opus 的意思,只回傳 JSON,包含 requestedProfile、effectiveProfile、runtimeEffort。',
229
+ "Explain what 'use fable on opus' means; no execution or model switching.",
230
+ 'Explain fable on sonnet; return JSON; no stages, delegation or execution.',
231
+ '請說明 fable on sonnet 的意思,並只回傳含 requestedProfile 的 JSON。',
232
+ 'Explain fable on sonnet, please return JSON.',
233
+ 'Explain fable on sonnet and please return only JSON.',
234
+ '請說明 fable on sonnet,請只回傳 JSON。',
235
+ 'Explain what “run fable on sonnect” means; return only JSON.',
236
+ 'Explain what ‘use fable on opus’ means.',
237
+ 'Explain fable on sonnet; no model switching.',
238
+ "Explain fable on sonnet; don't switch model or delegate.",
239
+ 'Explain fable on sonnet; no stages and no delegation.',
240
+ '解釋 fable on sonnet,不要委派或切換模型。',
241
+ ]) {
242
+ const lookup = runTier(prompt);
243
+ const repeat = runTier(prompt);
244
+ if (validContract(lookup, 'bounded Fable lookup')) {
245
+ check(lookup.contract.classification.tier === 'tier1', 'lookup remains a bounded Tier 1 operation');
246
+ check(lookup.contract.workflowPlan.strategy === 'direct-single', 'lookup selects direct-single before loading references');
247
+ check(lookup.contract.workflowPlan.parallelism.allowed === false, 'lookup does not delegate');
248
+ check(lookup.result.stdout.includes('references/profile-lookup.md'), 'lookup selects only compact reference');
249
+ check(!lookup.result.stdout.includes('RECOMMENDED KNOWLEDGE GUIDES'), 'lookup omits unrelated keyword guides');
250
+ check(!lookup.result.stdout.includes('with fable-mode/scripts/model-selector.js'), 'lookup does not send agent to selector implementation');
251
+ check(JSON.stringify(lookup.contract) === JSON.stringify(repeat.contract), 'lookup contract is deterministic');
252
+ }
253
+ }
254
+ for (const prompt of [
255
+ 'Use fable on sonnect to implement a multi-stage architecture migration.',
256
+ 'Explain fable on opus and then implement a repository-wide migration.',
257
+ 'Explain how to run fable-staged across all modules.',
258
+ 'Explain fable on sonnet then run it to analyze security.',
259
+ 'Explain fable on sonnet and analyze the authentication design.',
260
+ 'Explain fable on sonnet; then send the findings to Slack.',
261
+ 'Explain fable on sonnet and update README.',
262
+ 'Explain fable on haiku, add a regression test.',
263
+ 'Explain fable on opus and commit the documentation change.',
264
+ '說明 fable on sonnet 並更新 README。',
265
+ '解釋 fable on haiku,新增回歸測試。',
266
+ '解析 fable on opus 並刪除過期文件。',
267
+ 'Explain fable on sonnet before updating README.',
268
+ 'Explain fable on sonnet; return JSON after updating README.',
269
+ "Explain fable on sonnet before updating README.md to change 'Instalation' to 'Installation'.",
270
+ '解釋 fable on sonnet 後更新 README。',
271
+ 'Explain fable on sonnet; no delegation before deleting stale files.',
272
+ 'Explain fable on sonnet; return JSON with requestedProfile, update README.',
273
+ 'Explain fable on sonnet; output JSON with requestedProfile and then publish the result.',
274
+ 'Explain fable on sonnet; return JSON after restarting the server.',
275
+ 'Explain fable on sonnet; no execution except updating README.',
276
+ 'Explain fable on sonnet; provide a profile result while running the tests.',
277
+ 'Explain fable on sonnet; return JSON with unknownField.',
278
+ 'Explain fable on sonnet; some unknown continuation.',
279
+ // A negation scopes only its own noun list; affirmative actions after it execute.
280
+ 'Explain fable on sonnet; no delegation, do execution.',
281
+ 'Explain fable on sonnet; no delegation, switch model.',
282
+ 'Explain fable on sonnet; no delegation and stage execution.',
283
+ 'Explain fable on sonnet; without orchestration do execution.',
284
+ 'Explain fable on sonnet; no delegation or switch model.',
285
+ '解釋 fable on sonnet,不要委派,執行。',
286
+ '解釋 fable on sonnet,不要委派,切換模型。',
287
+ ]) {
288
+ const execution = runTier(prompt);
289
+ if (validContract(execution, 'Fable execution boundary')) {
290
+ check(execution.contract.workflowPlan.strategy === 'fable-staged', 'execution/macro scope preserves staged topology');
291
+ check(!execution.contract.workflowPlan.reasonCodes.includes('fable-profile-lookup'), 'mixed execution never records the bounded lookup reason');
292
+ check(execution.contract.workflowPlan.requiredInvariants.includes('isolated-worktree-before-mutation'), 'execution retains isolation obligation');
293
+ }
209
294
  }
210
295
 
211
296
  const explicitOverride = runTier('Use iterative-single to audit the entire repository architecture.');
@@ -250,10 +335,10 @@ if (validContract(noWorkspaceState, 'workspace state block')) {
250
335
  const unavailableModel = runTier('fable on opus audit the entire repository architecture', {
251
336
  context: { hostCapabilities: { modelAvailability: 'unavailable', subagents: 'available' } },
252
337
  });
253
- if (validContract(unavailableModel, 'model capability block')) {
254
- check(unavailableModel.contract.workflowPlan.modelSelection.requested === 'opus', 'router never remaps requested branded model');
255
- check(unavailableModel.contract.workflowPlan.fallback.disposition === 'blocked', 'known unavailable model is visibly blocked');
256
- check(unavailableModel.contract.workflowPlan.fallback.reasonCodes.includes('requested-model-capability-unavailable'), 'model capability block reason is recorded');
338
+ if (validContract(unavailableModel, 'runtime model capability advisory')) {
339
+ check(unavailableModel.contract.workflowPlan.profileSelection.requested === 'orchestrator', 'opus remains a behavior-profile alias');
340
+ check(unavailableModel.contract.workflowPlan.fallback.disposition !== 'blocked', 'runtime model availability does not block Fable profile selection');
341
+ check(!unavailableModel.contract.workflowPlan.fallback.reasonCodes.includes('requested-model-capability-unavailable'), 'router no longer treats a branded model as a Fable requirement');
257
342
  }
258
343
 
259
344
  const hookUnavailable = runTier('Drop the prod database table.', {
@@ -38,6 +38,14 @@ This is a **repository/agent contract**, not an automatic upgrade to any platfor
38
38
 
39
39
  The full ten-dimension entries and official source URLs are in [platform-compatibility.json](platform-compatibility.json). “Supported” in the standalone-skills column refers to the host’s documented skill surface plus the repository’s verified path contract; it does not mean that a live Harness session was run on every platform.
40
40
 
41
+ ### Claude Code Fable entrypoint boundary
42
+
43
+ The Claude plugin registers `./fable-mode` as the public Fable skill. The nested `fable-opus`, `fable-sonnet`, `fable-haiku`, and `execution-guardrails` directories are reference contracts inside that skill tree; they are not independently registered or host-routable Claude skills. The legacy prompts `fable on opus`, `fable on sonnet`/`sonnect`, and `fable on haiku` select Fable behavior profiles (orchestrator/reasoning/mechanical), not branded runtime models. `model-selector.js` records the profile and separately evaluates the host-specific advisory runtime floor; a below-floor or unknown runtime does not block Fable by itself.
44
+
45
+ The router distinguishes bounded profile explanations from Fable execution. Supported lookup prompts select `direct-single` and `references/profile-lookup.md`; the public skill dispatches before loading stage references. Entire explanation, format and negative-constraint clauses are checked against a bounded lookup vocabulary; comma/and-separated canonical output fields remain lookup data. Unknown continuations, same-clause actions (including `before`/`after`), mixed execution or macro scope retain execution routing and required invariants. This is a deterministic routing and packaged instruction contract, not a host-enforced reference-read limit or a measured token-cost guarantee.
46
+
47
+ The nested execution-guardrails file is also not an always-on carrier. Fable agents that need those rules carry them inline; any non-Fable surface must load, copy, or inject the rules through a carrier the host actually executes before claiming that they apply there.
48
+
41
49
  ## Installer target contract
42
50
 
43
51
  The general installer’s target paths are tested independently from host discovery. This prevents a successful file copy from being reported as proof that an agent will load the file.
@@ -36,18 +36,19 @@ stateDiagram-v2
36
36
  Recorded --> [*]
37
37
  ```
38
38
 
39
- The stage record preserves the requested/effective model, fallback reason, stage brief, artifact, pass condition, verification command, and verifier result.
39
+ The stage record preserves requested/effective behavior profile, profile alias, runtime model/effort when known, runtime-floor status, fallback reason, stage brief, artifact, pass condition, verification command, and verifier result.
40
40
 
41
- ## 3. Model Selection and Delegation
41
+ ## 3. Behavior Profile and Runtime Recommendation
42
42
 
43
- Explicit model requests are resolved through `model-selector.js`. Fable never silently downgrades a requested model.
43
+ Explicit `haiku`/`sonnet`/`sonnect`/`opus` requests are behavior-profile aliases resolved through `model-selector.js`; they do not pin the host runtime model. Host-specific model floors are advisory and visible.
44
44
 
45
45
  ```mermaid
46
46
  flowchart LR
47
- Requested[Requested model] --> Selector[model-selector.js]
48
- Selector -->|Available| Named[Named fable agent]
49
- Selector -->|Fallback allowed| Fallback[Recorded fallback]
50
- Selector -->|No valid fallback| Blocked[Escalate blocked stage]
47
+ Requested[Requested profile / legacy alias] --> Selector[model-selector.js]
48
+ Selector --> Role[Named Fable role]
49
+ Role --> Runtime[Host runtime + advisory model floor]
50
+ Runtime -->|Meets / below / unknown| Execute[Execute same behavior profile]
51
+ Role -->|Named agent unavailable| Fallback[Inline or stop fallback]
51
52
  Named --> Stage[Bounded stage brief]
52
53
  Fallback --> Stage
53
54
  Stage --> Verify[Named stage check]
@@ -56,7 +57,7 @@ flowchart LR
56
57
  Cold --> Record
57
58
  ```
58
59
 
59
- The intended role split is explicit rather than magical: Opus is suitable for orchestration, Sonnet for reasoning-heavy stage work, and Haiku for bounded mechanical work. Workers do not spawn workers.
60
+ The role split is explicit: `opus` aliases the orchestrator profile, `sonnet` aliases reasoning, and `haiku` aliases mechanical work. These names describe Fable behavior; actual Claude/Codex model choice is a separate host concern. Workers do not spawn workers.
60
61
 
61
62
  ## 4. Scope and Context Discipline
62
63
 
@@ -83,10 +84,10 @@ worker. A material confirmed blocker may stop the current stage immediately. Rep
83
84
  matching execution failures use the separate Rule-of-3 path: zoom out, reflect, then
84
85
  `RESUME` on a new approach or `ESCALATE` when a real decision is required.
85
86
 
86
- At most two full replans are allowed before unresolved blockers are escalated rather than hidden behind endless restructuring.
87
+ Replan counts (`maxReplans`) are advisory and never halt execution on their own; unresolved blockers are escalated rather than hidden behind endless restructuring, and three same-signature failures still trigger the mandatory Rule-of-3 `zoom-out`.
87
88
 
88
89
  ## 5. Completion Boundary
89
90
 
90
91
  Fable completion means the planned stages have produced their artifacts, their checks have passed, and high-risk outputs have received skeptical review where required. It does not mean every possible companion skill was invoked.
91
92
 
92
- The canonical executable and policy details live in `fable-mode/SKILL.md`, `fable-mode/model-matrix.json`, `fable-mode/scripts/model-selector.js`, and `fable-mode/references/model-matrix.md`.
93
+ The canonical executable and policy details live in `fable-mode/SKILL.md`, `fable-mode/behavior-profile-matrix.json`, `fable-mode/runtime-model-floor-matrix.json`, `fable-mode/scripts/model-selector.js`, and `fable-mode/references/model-matrix.md`.