harness-everything 0.30.10 → 0.31.0

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 (134) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +1 -1
  4. package/CHANGELOG.md +24 -4
  5. package/README.md +8 -9
  6. package/VERIFICATION.md +1 -1
  7. package/benchmarks/results/live-host/claude-2026-10-08-issue-297/README.md +27 -0
  8. package/benchmarks/results/pr-303-review-2026-10-13/README.md +81 -0
  9. package/benchmarks/results/pr-303-review-2026-10-13/ci/run.json +1330 -0
  10. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-clean-env.log +1 -0
  11. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-inherited-flag.log +34 -0
  12. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-captured.json +46 -0
  13. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-diagnosis.md +13 -0
  14. package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate.json +46 -0
  15. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/summary.json +57 -0
  16. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-collision.log +8 -0
  17. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-consistency.log +624 -0
  18. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-docs-capabilities.log +131 -0
  19. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-platform-compatibility.log +9 -0
  20. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-openai.log +6 -0
  21. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-submission.log +5 -0
  22. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-references.log +6 -0
  23. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-release.log +73 -0
  24. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-repo-contract.log +5 -0
  25. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-invariants.log +785 -0
  26. package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-skills.log +44 -0
  27. package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/summary.json +12 -0
  28. package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-consistency.log +624 -0
  29. package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-release.log +73 -0
  30. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/summary.json +79 -0
  31. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mechanism.log +1737 -0
  32. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mutations.log +16 -0
  33. package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test.log +3297 -0
  34. package/benchmarks/results/pr-303-review-2026-10-13/manifest.json +319 -0
  35. package/benchmarks/results/pr-303-review-2026-10-13/regression/binding-resolution-green.log +36 -0
  36. package/benchmarks/results/pr-303-review-2026-10-13/regression/determinism.json +174 -0
  37. package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-evidence.json +314 -0
  38. package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-report.json +638 -0
  39. package/benchmarks/results/pr-303-review-2026-10-13/regression/red.log +240 -0
  40. package/benchmarks/results/pr-303-review-2026-10-13/regression/untyped-fable-probe.json +22 -0
  41. package/benchmarks/results/pr-303-review-2026-10-13/regression/workflow-obligations-green.log +254 -0
  42. package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-clean-env-fixed.log +1 -0
  43. package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-inherited-flag-fixed.log +1 -0
  44. package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.meta.json +8 -0
  45. package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stderr.log +90 -0
  46. package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stdout.log +3186 -0
  47. package/benchmarks/results/pr-303-review-2026-10-13/verification/verify-gate-final.json +46 -0
  48. package/bin/cli.js +1 -1
  49. package/ci/invariant-routing.test.js +7 -4
  50. package/ci/mechanism-2ag-router-worktree-contract.test.js +2 -2
  51. package/ci/mechanism-2ai-workspace-fingerprint-batch.test.js +2 -1
  52. package/ci/mechanism-2an-workflow-obligations.test.js +352 -3
  53. package/ci/mechanism-2aq-fable-stage-bindings.test.js +168 -0
  54. package/ci/mechanism-2ar-binding-resolution.test.js +211 -0
  55. package/ci/mechanism-35-verification-contract.test.js +1 -1
  56. package/ci/mutation-check.js +2 -0
  57. package/ci/mutation-loader.js +4 -0
  58. package/ci/openai-plugin-package.test.js +7 -5
  59. package/ci/prompt-contract-regressions.test.js +7 -6
  60. package/ci/router-workflow-plan.test.js +109 -14
  61. package/ci/skill-routing-check.js +44 -15
  62. package/ci/workflow-plan-consumers.test.js +41 -1
  63. package/docs/architecture.md +10 -10
  64. package/docs/enforcement-locks.md +1 -1
  65. package/docs/mechanism-first-skill-mesh.md +12 -11
  66. package/docs/openai-plugin.md +1 -1
  67. package/docs/philosophy.md +7 -6
  68. package/docs/platform-capabilities.md +7 -5
  69. package/docs/routing.md +16 -17
  70. package/docs/system-one-observations.md +1 -1
  71. package/docs/system-one-routing.md +10 -7
  72. package/docs/system-one-suggestion-gates.md +5 -5
  73. package/docs/workflow-runtime.md +6 -2
  74. package/docs/workflows/harness-everything.md +14 -14
  75. package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
  76. package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
  77. package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
  78. package/fable-mode/CONTRACT-FORMAT.md +4 -1
  79. package/fable-mode/SKILL.md +6 -8
  80. package/fable-mode/agents/fable-orchestrator.md +10 -3
  81. package/fable-mode/execution-guardrails/SKILL.md +1 -1
  82. package/fable-mode/fable-haiku/SKILL.md +1 -1
  83. package/fable-mode/fable-opus/SKILL.md +1 -1
  84. package/fable-mode/fable-sonnet/SKILL.md +1 -1
  85. package/fable-mode/scripts/workflow-plan-consumer.js +101 -2
  86. package/harness-everything/SKILL.md +8 -7
  87. package/harness-everything/references/router-workflow-plan.md +11 -6
  88. package/harness-everything/references/triage-and-tiers.md +21 -23
  89. package/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
  90. package/harness-everything/scripts/kernel-router-core.js +52 -55
  91. package/harness-everything/scripts/kernel-router.js +48 -5
  92. package/harness-everything/scripts/router-contract-core.js +7 -7
  93. package/harness-everything/scripts/router-contract.js +1 -3
  94. package/harness-everything/scripts/routing-keywords.json +417 -121
  95. package/harness-everything/scripts/tier-router.js +64 -113
  96. package/hooks/scripts/contract-test.js +29 -3
  97. package/hooks/scripts/lib/binding-resolver.js +76 -0
  98. package/hooks/scripts/lib/fable-contracts.js +1 -1
  99. package/hooks/scripts/lib/workflow-obligations.js +292 -7
  100. package/hooks/scripts/lib/workflow-runtime.js +8 -1
  101. package/hooks/scripts/workflow-disposition.js +116 -2
  102. package/opencode-plugin/plugin.json +1 -1
  103. package/package.json +1 -1
  104. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  105. package/plugins/harness-everything/hooks/scripts/contract-test.js +29 -3
  106. package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +76 -0
  107. package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +1 -1
  108. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +292 -7
  109. package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +8 -1
  110. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +116 -2
  111. package/plugins/harness-everything/plugin.json +1 -1
  112. package/plugins/harness-everything/scripts/lib/advisory-text.js +2 -2
  113. package/plugins/harness-everything/scripts/lib/generated-skills.js +78 -0
  114. package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +4 -1
  115. package/plugins/harness-everything/skills/fable-mode/SKILL.md +6 -8
  116. package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +10 -3
  117. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
  118. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
  119. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
  120. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
  121. package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +101 -2
  122. package/plugins/harness-everything/skills/harness-everything/SKILL.md +8 -7
  123. package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +11 -6
  124. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +21 -23
  125. package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
  126. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +52 -55
  127. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +48 -5
  128. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +7 -7
  129. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +1 -3
  130. package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +417 -121
  131. package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +64 -113
  132. package/sbom.cdx.json +7 -7
  133. package/scripts/lib/advisory-text.js +2 -2
  134. package/scripts/lib/generated-skills.js +78 -0
@@ -15,6 +15,7 @@ const {
15
15
  FABLE_MAX_WORKERS,
16
16
  validateRouterContract,
17
17
  } = require(path.join(ROOT, 'harness-everything', 'scripts', 'router-contract.js'));
18
+ const { splitProfileLookupClauses } = require(tierRouter);
18
19
  let failed = 0;
19
20
 
20
21
  function check(condition, message) {
@@ -83,9 +84,11 @@ if (validContract(iterative, 'iterative-single')) {
83
84
  check(plan.limits.maxWorkers === FABLE_MAX_WORKERS, 'workflow exposes a MAY worker planning value');
84
85
  check(plan.requiredInvariants.includes('objective-verification'), 'iterative-single carries objective verification invariant');
85
86
  check(plan.requiredInvariants.includes('loop-awareness'), 'iterative-single carries semantic loop-awareness obligation while numeric counts remain MAY');
86
- check(plan.suggestedSkills.includes('tdd'), 'TDD remains applicability-conditional rather than a universal invariant');
87
+ check(plan.knowledgeSignals.includes('tdd-test'), 'test-related keywords become normalized step input signals');
88
+ check(plan.suggestedSkills.length === 0, 'selected topology no longer pushes a global skill list');
89
+ check(!plan.knowledgeSignals.some(signal => signal.includes('/') || signal.endsWith('.md')), 'knowledge signals do not directly select documents');
87
90
  check(plan.memory.write === 'none', 'ordinary iterative work cannot write durable memory');
88
- check(!plan.requiredInvariants.includes('tdd'), 'suggested skill names remain separate from plan invariants until applicability is resolved');
91
+ check(!plan.requiredInvariants.includes('tdd'), 'binding ids remain step-local rather than workflow invariants');
89
92
  }
90
93
 
91
94
  const memoryPersist = runTier('Persist this lesson as memory after resolving the checkout regression.');
@@ -94,7 +97,7 @@ if (validContract(memoryPersist, 'self-evolve persistence')) {
94
97
  check(plan.strategy === 'direct-single', 'bounded explicit persistence request receives a selected workflow');
95
98
  check(plan.memory.write === 'persist-via-self-evolve', 'explicit persistence request authorizes only self-evolve durable write');
96
99
  check(plan.requiredInvariants.includes('memory-write-authorization'), 'durable memory route carries runtime authorization invariant');
97
- check(plan.suggestedSkills.includes('self-evolve'), 'durable memory route suggests self-evolve explicitly');
100
+ check(plan.suggestedSkills.length === 0, 'memory authorization does not add a global self-evolve suggestion');
98
101
  check(memoryPersist.contract.taskShape.observedSignals.memoryPersistenceRequested === true, 'persistence intent is recorded in task shape');
99
102
  }
100
103
 
@@ -141,7 +144,8 @@ if (validContract(staged, 'fable-staged')) {
141
144
  check(plan.parallelism.allowed === false, 'dependent stages remain sequential');
142
145
  check(plan.verification.mode === 'cold-verifier', 'fable-staged requires cold verification');
143
146
  check(plan.requiredInvariants.includes('isolated-worktree-before-mutation'), 'fable-staged carries mandatory isolation disposition');
144
- check(plan.suggestedSkills.includes('using-git-worktrees'), 'fable-staged surfaces worktree resolution skill');
147
+ check(plan.requiredInvariants.includes('isolated-worktree-before-mutation'), 'fable-staged keeps its worktree isolation invariant');
148
+ check(plan.suggestedSkills.length === 0, 'fable topology does not globally push worktree/fable skills');
145
149
  }
146
150
 
147
151
  const parallelPrompt = 'Audit the entire repository with independent read-only security, architecture, and documentation workstreams.';
@@ -218,6 +222,13 @@ if (validContract(explicitSonnect, 'explicit Fable sonnect alias')) {
218
222
 
219
223
  // #290: mentioning a profile in a bounded lookup must not launch orchestration.
220
224
  for (const prompt of [
225
+ 'Could you please explain fable on sonnet? Return only JSON.',
226
+ 'Please, explain fable on sonnet; return only JSON.',
227
+ 'What is fable on opus? Return only JSON.',
228
+ 'Explain what fable on sonnet means.Return only JSON.',
229
+ 'Could you please explain fable on sonnet as a compact JSON record.',
230
+ '說明 fable on sonnet,不必啟動任何流程,請只輸出 JSON。',
231
+ '解釋 fable on opus,無須進入任何階段,請回傳 JSON。',
221
232
  "Explain what 'fable on sonnect' means; return only a small JSON selection record.",
222
233
  "Resolve fable on haiku into a profile JSON record; no orchestration or delegation.",
223
234
  "解釋 fable on opus 的意思,只回傳 JSON。",
@@ -242,13 +253,13 @@ for (const prompt of [
242
253
  const lookup = runTier(prompt);
243
254
  const repeat = runTier(prompt);
244
255
  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');
256
+ check(lookup.contract.classification.tier === 'tier1', `${prompt}: lookup remains a bounded Tier 1 operation`);
257
+ check(lookup.contract.workflowPlan.strategy === 'direct-single', `${prompt}: lookup selects direct-single before loading references`);
247
258
  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');
259
+ check(lookup.result.stdout.includes('references/profile-lookup.md'), `${prompt}: lookup selects only compact reference`);
260
+ check(!lookup.result.stdout.includes('RECOMMENDED KNOWLEDGE GUIDES'), `${prompt}: lookup omits unrelated keyword guides`);
261
+ check(!lookup.result.stdout.includes('with fable-mode/scripts/model-selector.js'), `${prompt}: lookup does not send agent to selector implementation`);
262
+ check(JSON.stringify(lookup.contract) === JSON.stringify(repeat.contract), `${prompt}: lookup contract is deterministic`);
252
263
  }
253
264
  }
254
265
  for (const prompt of [
@@ -351,8 +362,16 @@ if (validContract(hookUnavailable, 'actionGate capability block')) {
351
362
  }
352
363
 
353
364
  const guideDedup = runTier('security audit login authentication');
354
- const securityGuideCount = (guideDedup.result.stdout.match(/security-review\/SKILL\.md/g) || []).length;
355
- check(securityGuideCount === 1, 'duplicate guide matches emit security-review/SKILL.md exactly once by path');
365
+ check(guideDedup.contract.workflowPlan.knowledgeSignals.filter(signal => signal === 'security').length === 1,
366
+ 'overlapping keywords deduplicate normalized security input signals');
367
+ check(!guideDedup.result.stdout.includes('security-review/SKILL.md'), 'keyword matching does not print a document path');
368
+ check(!guideDedup.result.stdout.includes('RECOMMENDED KNOWLEDGE GUIDES'), 'keyword matching no longer pushes guide lists');
369
+
370
+ const dottedLookupClauses = splitProfileLookupClauses('Explain what fable on sonnet means in README.md version v1.2.Return only JSON.');
371
+ check(dottedLookupClauses[0].includes('README.md') && dottedLookupClauses[0].includes('v1.2'),
372
+ 'profile lookup clause splitting keeps filenames and dotted versions intact');
373
+ check(dottedLookupClauses.length === 2 && dottedLookupClauses[1] === 'Return only JSON',
374
+ 'profile lookup clause splitting recognizes a no-space output clause boundary');
356
375
 
357
376
  const repeatA = runTier('Fix this checkout bug and add a regression test.');
358
377
  const repeatB = runTier('Fix this checkout bug and add a regression test.');
@@ -395,7 +414,7 @@ const kernelNotificationPersistFailure = spawnSync(process.execPath, [kernelRout
395
414
  env: { ...process.env, HARNESS_STATE_HOME: notificationStateFile, HARNESS_WORKSPACE_ROOT: ROOT },
396
415
  });
397
416
  check(kernelNotificationPersistFailure.status === 0, 'host notification persistence failure stays fail-open');
398
- check(kernelNotificationPersistFailure.stdout.includes('Host notification — no active execution contract; ignored for routing.'), 'host notification remains a routing no-op when state persistence fails');
417
+ check(kernelNotificationPersistFailure.stdout.includes('Host notification: no active execution contract; ignored for routing.'), 'host notification remains a routing no-op when state persistence fails');
399
418
  check(kernelNotificationPersistFailure.stderr.includes('routing state was not persisted'), 'host notification persistence failure remains visible as a reminder');
400
419
  fs.rmSync(notificationStateHome, { recursive: true, force: true });
401
420
 
@@ -407,7 +426,83 @@ check(kernelIterative.stdout.includes('"strategy":"iterative-single"'), 'kernel
407
426
  check(kernelIterative.stdout.includes('loop-awareness:'), 'kernel prints dynamic semantic plan obligations');
408
427
  check(kernelIterative.stdout.includes('visible-status-updates:'), 'kernel prints the user-visible status invariant');
409
428
  check(kernelIterative.stdout.includes('USER-VISIBLE HARNESS STATUS CONTRACT (MUST)'), 'kernel prints the canonical mandatory user-visible status contract');
410
- check(kernelIterative.stdout.includes(' - tdd'), 'kernel prints suggested skills separately from required invariants');
429
+ check(kernelIterative.stdout.includes('KNOWLEDGE SIGNALS (STEP INPUT ONLY)'), 'kernel identifies knowledge signals as planning inputs');
430
+ check(!kernelIterative.stdout.includes('tdd/SKILL.md'), 'initial router output waits for an active step before disclosing a skill path');
431
+ check(!kernelIterative.stdout.includes('evaluate-suggestions-before-skip'), 'legacy read-every-suggestion invariant is removed');
432
+
433
+ const retainedStateHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-router-retained-step-'));
434
+ const retainedSession = 'issue297-retained-step';
435
+ const retainedEnv = {
436
+ ...process.env,
437
+ HARNESS_STATE_HOME: retainedStateHome,
438
+ HARNESS_WORKSPACE_ROOT: ROOT,
439
+ };
440
+ const retainedInput = prompt => JSON.stringify({ session_id: retainedSession, cwd: ROOT, hook_event_name: 'UserPromptSubmit', prompt });
441
+ const initialRetainedRoute = spawnSync(process.execPath, [kernelRouter], {
442
+ cwd: ROOT,
443
+ encoding: 'utf8',
444
+ input: retainedInput('Fix this checkout bug and add a regression test.'),
445
+ env: retainedEnv,
446
+ });
447
+ check(initialRetainedRoute.status === 0, 'retained workflow fixture routes successfully');
448
+ check(initialRetainedRoute.stdout.includes('PLANNING STEP: decompose → compose'), 'new selected workflow exposes its planning step');
449
+
450
+ const sessionPathResult = spawnSync(process.execPath, ['-e', "const s=require('./hooks/scripts/lib/harness-state');process.stdout.write(s.getSessionDir(process.env.HARNESS_WORKSPACE_ROOT, process.env.TEST_SESSION_ID, {session_id:process.env.TEST_SESSION_ID,cwd:process.env.HARNESS_WORKSPACE_ROOT}))"], {
451
+ cwd: ROOT,
452
+ encoding: 'utf8',
453
+ env: { ...retainedEnv, TEST_SESSION_ID: retainedSession },
454
+ });
455
+ const retainedSessionDir = sessionPathResult.stdout;
456
+ const retainedController = path.join(ROOT, 'hooks', 'scripts', 'workflow-disposition.js');
457
+ const typedRequirements = JSON.stringify([
458
+ { id: 'req-review', stepType: 'review', summary: 'Review the checkout behavior', acceptance: 'the affected behavior is understood' },
459
+ { id: 'req-change', stepType: 'behavior-change', summary: 'Change the checkout behavior', acceptance: 'the requested behavior is corrected' },
460
+ { id: 'req-verify', stepType: 'verification', summary: 'Verify the checkout behavior', acceptance: 'objective verification evidence is recorded' },
461
+ ]);
462
+ const planCommand = spawnSync(process.execPath, [retainedController, 'plan', '--session-id', retainedSession,
463
+ '--requirements-json', typedRequirements, '--strategy', 'iterative-single', '--evidence', 'three ordered requirement steps were confirmed'], {
464
+ cwd: ROOT, encoding: 'utf8', env: retainedEnv,
465
+ });
466
+ check(planCommand.status === 0, 'retained workflow records typed requirements before execution');
467
+ const startCommand = spawnSync(process.execPath, [retainedController, 'start', '--session-id', retainedSession], {
468
+ cwd: ROOT, encoding: 'utf8', env: retainedEnv,
469
+ });
470
+ const startedRetained = JSON.parse(startCommand.stdout || '{}');
471
+ check(startCommand.status === 0 && startedRetained.activeStep?.id === 'req-review', 'retained workflow starts at its first typed step');
472
+ check(startedRetained.activeStepCommands?.resolveBinding.includes('--step-id "req-review"') &&
473
+ startedRetained.activeStepCommands?.passStep.includes('--step-id "req-review"'),
474
+ 'start returns concrete active binding and step disposition commands');
475
+ const reviewCommand = spawnSync(process.execPath, [retainedController, 'step', '--session-id', retainedSession,
476
+ '--step-id', 'req-review', '--disposition', 'pass', '--evidence', 'the affected behavior and request were reviewed'], {
477
+ cwd: ROOT, encoding: 'utf8', env: retainedEnv,
478
+ });
479
+ check(reviewCommand.status === 0, 'retained workflow advances to its active behavior-change step');
480
+
481
+ const retainedWorkflowPath = path.join(retainedSessionDir, 'workflow-run.json');
482
+ const retainedObligationsPath = path.join(retainedSessionDir, 'workflow-obligations.json');
483
+ const retainedBeforeNotification = {
484
+ workflow: fs.readFileSync(retainedWorkflowPath, 'utf8'),
485
+ obligations: fs.readFileSync(retainedObligationsPath, 'utf8'),
486
+ };
487
+ const hostNotification = spawnSync(process.execPath, [kernelRouter], {
488
+ cwd: ROOT,
489
+ encoding: 'utf8',
490
+ input: retainedInput('<task-notification><task-id>active-step-reminder</task-id><status>completed</status></task-notification>'),
491
+ env: retainedEnv,
492
+ });
493
+ check(hostNotification.status === 0, 'host notification retains the active workflow');
494
+ check(hostNotification.stdout.includes('req-change') && hostNotification.stdout.includes('tdd/SKILL.md'),
495
+ 'retained notification preserves only the active step and its binding');
496
+ check(hostNotification.stdout.includes('binding --session-id "issue297-retained-step" --step-id "req-change"') &&
497
+ hostNotification.stdout.includes('step --session-id "issue297-retained-step" --step-id "req-change"'),
498
+ 'retained notification surfaces the binding and step disposition syntax');
499
+ check(!hostNotification.stdout.includes('verification-loop/SKILL.md'), 'retained notification hides future-step bindings');
500
+ check(!hostNotification.stdout.includes('ROUTER WORKFLOW PLAN (JSON)') && !hostNotification.stdout.includes('REQUIRED HARNESS INVARIANTS (SEMANTIC MUST)'),
501
+ 'host notification avoids repeating the full workflow contract');
502
+ check(retainedBeforeNotification.workflow === fs.readFileSync(retainedWorkflowPath, 'utf8') &&
503
+ retainedBeforeNotification.obligations === fs.readFileSync(retainedObligationsPath, 'utf8'),
504
+ 'host notification does not replace or advance workflow state');
505
+ fs.rmSync(retainedStateHome, { recursive: true, force: true });
411
506
 
412
507
  for (const schemaPath of [
413
508
  'harness-everything/schemas/router-task-shape.schema.json',
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Deterministic route coverage gate for directly-routable skills plus an
4
- * explicit classification gate for nested skills. A new nested SKILL.md must
5
- * declare whether it is parent-routed or internal; otherwise CI fails instead
6
- * of silently ignoring it.
3
+ * Deterministic coverage gate for positive skill evals plus an explicit
4
+ * classification gate for nested skills. Each positive prompt must surface the
5
+ * skill as a candidate step-binding id (relevance), while the router must not
6
+ * push canonical skill paths before an active step binds them. A new nested
7
+ * SKILL.md must declare whether it is parent-routed or internal; otherwise CI
8
+ * fails instead of silently ignoring it.
7
9
  */
8
10
 
9
11
  const fs = require('fs');
@@ -22,6 +24,18 @@ const NESTED_ROUTING = new Map([
22
24
  ['fable-mode/fable-opus', 'parent'],
23
25
  ]);
24
26
 
27
+ // Skills whose positive evals prove a route shape rather than a binding: the
28
+ // router itself and the todo-driven multi-step strategy.
29
+ const ROUTE_SHAPE_SKILLS = new Map([
30
+ ['harness-everything', output => /=> RECOMMENDED TIER: \S/.test(output)],
31
+ ['todo-driven-workflow', output => /=> WORKFLOW STRATEGY: (?!deferred)\S+/.test(output)],
32
+ ]);
33
+
34
+ function candidateBindingIds(output) {
35
+ const line = output.match(/Candidate step binding ids: ([^\r\n]+)/);
36
+ return line ? line[1].split(',').map(id => id.trim()).filter(Boolean) : [];
37
+ }
38
+
25
39
  function walkPositiveTasks(dir) {
26
40
  if (!fs.existsSync(dir)) return [];
27
41
  const files = [];
@@ -47,12 +61,6 @@ function discoverNestedSkills(skillDir) {
47
61
  return found;
48
62
  }
49
63
 
50
- function expectedMarker(skill) {
51
- if (skill === 'harness-everything') return 'RECOMMENDED TIER';
52
- if (skill === 'todo-driven-workflow') return 'WORKFLOW STRATEGY';
53
- return `${skill}/SKILL.md`;
54
- }
55
-
56
64
  const skillDirs = fs.readdirSync(ROOT, { withFileTypes: true })
57
65
  .filter(entry => entry.isDirectory() && fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md')))
58
66
  .map(entry => entry.name)
@@ -62,6 +70,8 @@ let failures = 0;
62
70
  let cases = 0;
63
71
 
64
72
  const nestedOnDisk = skillDirs.flatMap(skill => discoverNestedSkills(path.join(ROOT, skill))).sort();
73
+ const canonicalSkillPaths = [...skillDirs, ...nestedOnDisk]
74
+ .map(skill => `${skill.replace(/\\/g, '/')}/SKILL.md`);
65
75
  for (const nested of nestedOnDisk) {
66
76
  const classification = NESTED_ROUTING.get(nested);
67
77
  if (!classification) {
@@ -121,15 +131,34 @@ for (const skill of skillDirs) {
121
131
 
122
132
  const result = spawnSync(process.execPath, [ROUTER, prompt], { cwd: ROOT, encoding: 'utf8' });
123
133
  const output = `${result.stdout || ''}\n${result.stderr || ''}`;
124
- const marker = expectedMarker(skill);
125
- if (result.status !== 0 || !output.includes(marker)) {
126
- console.error(`FAIL ${path.relative(ROOT, taskFile)}: expected "${marker}"`);
134
+ const missingPlanFields = [
135
+ '=> RECOMMENDED TIER:',
136
+ '=> WORKFLOW STRATEGY:',
137
+ ].filter(marker => !output.includes(marker));
138
+ const requestedNested = nestedOnDisk
139
+ .filter(nested => NESTED_ROUTING.get(nested) === 'parent')
140
+ .filter(nested => (task.tags || []).includes(path.posix.basename(nested)));
141
+ const requiredParentPaths = requestedNested
142
+ .map(nested => `${nested.split('/')[0]}/SKILL.md`);
143
+ const missingParentPaths = requiredParentPaths.filter(skillPath => !output.includes(skillPath));
144
+ const leakedPaths = canonicalSkillPaths
145
+ .filter(skillPath => output.includes(skillPath) && !requiredParentPaths.includes(skillPath));
146
+ const routeShape = ROUTE_SHAPE_SKILLS.get(skill);
147
+ const relevant = routeShape ? routeShape(output) : candidateBindingIds(output).includes(skill);
148
+ if (result.status !== 0 || missingPlanFields.length || missingParentPaths.length || leakedPaths.length || !relevant) {
149
+ const reasons = [];
150
+ if (result.status !== 0) reasons.push(`router exited ${result.status}`);
151
+ if (missingPlanFields.length) reasons.push(`missing ${missingPlanFields.join(', ')}`);
152
+ if (!relevant) reasons.push(routeShape ? 'expected route shape not emitted' : `skill is not a candidate step binding (got: ${candidateBindingIds(output).join(', ') || 'none'})`);
153
+ if (missingParentPaths.length) reasons.push(`missing explicit parent route: ${missingParentPaths.join(', ')}`);
154
+ if (leakedPaths.length) reasons.push(`pre-step skill paths leaked: ${leakedPaths.join(', ')}`);
155
+ console.error(`FAIL ${path.relative(ROOT, taskFile)}: ${reasons.join('; ')}`);
127
156
  failures++;
128
157
  } else {
129
- console.log(`PASS ${skill}/${path.basename(taskFile)}`);
158
+ console.log(`PASS ${skill}/${path.basename(taskFile)}: skill-specific relevance proven without pre-step path leaks`);
130
159
  }
131
160
  }
132
161
  }
133
162
 
134
- console.log(`\nRoute coverage: ${cases} positive case(s) checked across ${skillDirs.length} direct skills; ${nestedOnDisk.length} nested skill(s) classified.`);
163
+ console.log(`\nPositive skill-eval coverage: ${cases} case(s) checked across ${skillDirs.length} direct skills; ${nestedOnDisk.length} nested skill(s) classified.`);
135
164
  process.exit(failures ? 1 : 0);
@@ -22,6 +22,7 @@ const {
22
22
  prepareRun,
23
23
  validateStageGraph,
24
24
  } = require(path.join(ROOT, 'fable-mode', 'scripts', 'workflow-plan-consumer.js'));
25
+ const { activeStageBindings } = require(path.join(ROOT, 'fable-mode', 'scripts', 'workflow-plan-consumer.js'));
25
26
  const { consumeWorkspacePlan } = require(path.join(ROOT, 'multi-agent-workspace', 'scripts', 'consume-workflow-plan.js'));
26
27
  const { pathWithinScope } = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'fable-contracts.js'));
27
28
 
@@ -70,17 +71,20 @@ const parallelStages = [
70
71
  {
71
72
  stageId: 'security', goal: 'read security state', agent: 'fable-worker-sonnet', task: 'audit security',
72
73
  inputs: [], expectedOutputs: ['security notes'], outputPath: null, dependsOn: [], writeSet: [],
74
+ requiredBindings: ['unknown-security-check'],
73
75
  checkCommand: 'node --test security.test.js', passCondition: 'exit 0',
74
76
  },
75
77
  {
76
78
  stageId: 'architecture', goal: 'read architecture state', agent: 'fable-worker-sonnet', task: 'audit architecture',
77
79
  inputs: [], expectedOutputs: ['architecture notes'], outputPath: null, dependsOn: [], writeSet: [],
80
+ optionalBindings: [{ id: 'architecture-reference', path: 'docs/architecture.md' }],
78
81
  checkCommand: 'node --test architecture.test.js', passCondition: 'exit 0',
79
82
  },
80
83
  {
81
84
  stageId: 'synthesis', goal: 'synthesize results', agent: 'fable-worker-sonnet', task: 'write synthesis',
82
85
  inputs: [], expectedOutputs: ['summary'], outputPath: 'reports/summary.md', dependsOn: ['security', 'architecture'],
83
- writeSet: ['reports/summary.md'], checkCommand: 'node --test synthesis.test.js', passCondition: 'exit 0',
86
+ writeSet: ['reports/summary.md'], requiredBindings: [{ id: 'verification-loop', path: 'verification-loop/SKILL.md' }],
87
+ checkCommand: 'node --test synthesis.test.js', passCondition: 'exit 0',
84
88
  },
85
89
  ];
86
90
 
@@ -95,6 +99,30 @@ check(JSON.stringify(runA.execution.batches) === JSON.stringify([['architecture'
95
99
  check(runA.execution.limits.maxWorkers === 4, 'run manifest retains maxWorkers as an advisory planning hint');
96
100
  check(fs.existsSync(path.join(runA.runRoot, 'contracts', 'security.json')), 'run-scoped stage contract is written');
97
101
  check(fs.existsSync(path.join(runA.runRoot, 'contracts', 'synthesis.json')), 'downstream stage has its own contract');
102
+ const initialActiveStages = activeStageBindings(runA.runRoot);
103
+ check(initialActiveStages.map(stage => stage.stageId).join(',') === 'security,architecture', 'only dependency-ready Fable stages expose bindings');
104
+ check(initialActiveStages[0].requiredBindings[0].path === null &&
105
+ initialActiveStages[0].requiredBindings[0].status === 'pending', 'unknown required binding remains visible and unresolved');
106
+ check(initialActiveStages[1].optionalBindings[0].path === 'docs/architecture.md' &&
107
+ initialActiveStages[1].optionalBindings[0].status === 'pending', 'active optional reference path appears only on its ready stage');
108
+ check(!JSON.stringify(initialActiveStages).includes('verification-loop/SKILL.md'), 'future-stage binding paths stay hidden until dependencies pass');
109
+ for (const stageId of ['security', 'architecture']) {
110
+ const file = path.join(runA.runRoot, 'contracts', `${stageId}.json`);
111
+ const contract = JSON.parse(fs.readFileSync(file, 'utf8'));
112
+ contract.status = 'pass';
113
+ fs.writeFileSync(file, JSON.stringify(contract));
114
+ }
115
+ const unresolvedDependencies = activeStageBindings(runA.runRoot);
116
+ check(!unresolvedDependencies.some(stage => stage.stageId === 'synthesis'), 'a stage marked pass with unresolved bindings does not activate dependents');
117
+ for (const stageId of ['security', 'architecture']) {
118
+ const file = path.join(runA.runRoot, 'contracts', `${stageId}.json`);
119
+ const contract = JSON.parse(fs.readFileSync(file, 'utf8'));
120
+ for (const binding of [...contract.requiredBindings, ...contract.optionalBindings]) binding.status = 'loaded';
121
+ fs.writeFileSync(file, JSON.stringify(contract));
122
+ }
123
+ const nextActiveStages = activeStageBindings(runA.runRoot);
124
+ check(nextActiveStages.map(stage => stage.stageId).join(',') === 'synthesis', 'downstream bindings appear only after every dependency passes');
125
+ check(nextActiveStages[0].requiredBindings[0].path === 'verification-loop/SKILL.md', 'current stage receives only its own declared binding path');
98
126
 
99
127
  const runB = prepareRun({
100
128
  routerContract: parallelContract,
@@ -143,6 +171,18 @@ expectThrow(() => validateStageGraph([
143
171
  { stageId: 'b', goal: 'b', agent: 'a', task: 'b', dependsOn: ['a'], writeSet: [], inputs: [], expectedOutputs: [] },
144
172
  ]), /cyclic/, 'dependency cycle is rejected');
145
173
 
174
+ expectThrow(() => validateStageGraph([
175
+ { stageId: 'unsafe-binding', goal: 'x', agent: 'a', task: 'x', dependsOn: [], writeSet: [], requiredBindings: [{ id: 'escaped', path: '../outside.md' }] },
176
+ ]), /repository-relative|escapes/, 'binding paths reject traversal outside the repository');
177
+
178
+ expectThrow(() => validateStageGraph([
179
+ { stageId: 'drive-binding', goal: 'x', agent: 'a', task: 'x', dependsOn: [], writeSet: [], requiredBindings: [{ id: 'drive', path: 'C:outside.md' }] },
180
+ ]), /repository-relative|escapes/, 'binding paths reject Windows drive-relative names');
181
+
182
+ expectThrow(() => validateStageGraph([
183
+ { stageId: 'duplicate-binding', goal: 'x', agent: 'a', task: 'x', dependsOn: [], writeSet: [], requiredBindings: ['same'], optionalBindings: ['same'] },
184
+ ]), /both required and optional/, 'a stage binding cannot be both required and optional');
185
+
146
186
  expectThrow(() => prepareRun({
147
187
  routerContract: parallelContract,
148
188
  workspaceRoot: workspace,
@@ -10,7 +10,7 @@ The minimal kernel establishes:
10
10
 
11
11
  - scope/tier before mutation,
12
12
  - a selected workflow topology as a semantic execution contract when task evidence supports one,
13
- - applicability evaluation for every suggested skill before omission,
13
+ - ordered requirement decomposition and step-scoped knowledge bindings,
14
14
  - objective evidence before completion,
15
15
  - bounded re-plan/recovery after repeated failure,
16
16
  - explicit warnings/degraded/escape evidence instead of silent workflow deletion;
@@ -18,7 +18,7 @@ The minimal kernel establishes:
18
18
 
19
19
  > **Contract-first lifecycle; flexible reasoning/implementation and execution.**
20
20
  >
21
- > Suggested skills MUST be evaluated from their real flows. Applicable skill core contracts and selected-topology obligations are semantic MUSTs; missing evidence produces reminders rather than persistent Harness locks.
21
+ > Active required bindings and selected-topology obligations are semantic MUSTs; keyword signals are planning inputs, and unrelated future bindings stay undisclosed.
22
22
 
23
23
  This avoids both extremes: no universal Tier-2/Tier-3 pipeline and no maze of self-deadlocking cognitive gates.
24
24
 
@@ -31,10 +31,9 @@ flowchart TD
31
31
  P --> A{Strategy selected?}
32
32
  A -- No / deferred --> D[Preserve invariants<br/>choose smallest justified workflow]
33
33
  A -- Yes --> C[ACTIVE workflow contract]
34
- C --> S{Suggested skills?}
35
- S -- Yes --> E[Read SKILL.md<br/>resolve applicability]
36
- S -- No --> X[Execute topology]
37
- E --> X
34
+ C --> D[Decompose ordered requirements]
35
+ D --> B[Bind skills/references per step]
36
+ B --> X[Execute active step/stage]
38
37
  X --> V{Required stage/check resolved?}
39
38
  V -- Yes --> Done[Evidence-backed completion]
40
39
  V -- No --> R[Diagnose / bounded re-plan]
@@ -49,7 +48,8 @@ flowchart TD
49
48
  - tier + rationale,
50
49
  - selected/deferred execution strategy,
51
50
  - required invariants,
52
- - suggested skills that require applicability evaluation,
51
+ - normalized knowledge signals for planning, not document selection,
52
+ - bindings only for the active step or dependency-ready Fable stage,
53
53
  - the internal routing checkpoint,
54
54
  - the semantic workflow contract.
55
55
 
@@ -74,9 +74,9 @@ Use nested evidence bullets when multiple items would otherwise become one dense
74
74
 
75
75
  This is a semantic contract, not another cognitive lock. Hook-capable hosts may observe or remind about compliance; instruction-only hosts still receive the MUST contract without pretending they can mechanically enforce it.
76
76
 
77
- ### Skill applicability vs. topology execution
77
+ ### Step-scoped knowledge vs. topology execution
78
78
 
79
- Router suggestions remain useful domain/workflow knowledge, not a universal sequence. Before omission, the agent reads the complete `SKILL.md` entry and evaluates `USE FOR`, `DO NOT USE FOR`, workflow/basic flow, and hard rules. Name/description/router-summary or “routine task” is insufficient evidence.
79
+ Keyword/domain matches are planning signals, not skill/reference paths. Requirement composition declares bindings on ordered steps; only active bindings are disclosed. A required skill binding MUST be read and its applicable core contract followed before the step passes. Unknown/unavailable paths remain visible and unresolved, while unrelated and future-step knowledge is not preloaded.
80
80
 
81
81
  The selected topology is a semantic lifecycle contract, not a persistent execution lock. Required obligations MUST resolve; implementation tactics MAY adapt.
82
82
 
@@ -89,7 +89,7 @@ For selected Fable topologies on Claude:
89
89
  - `workflow-gate.js` observes supported mutation/worktree/Fable state and emits reminders;
90
90
  - Fable owns stage contracts, `dependsOn`/`writeSet`, validated execution batches, objective per-stage checks, synthesis, cold verification, and bounded re-plans;
91
91
  - `workflow-stop-gate.js` reports unresolved stage/verification evidence without rejecting Stop;
92
- - `workflow-disposition.js` records explicit lifecycle/audit decisions; numeric budgets are no longer authoritative control state.
92
+ - `workflow-disposition.js` records explicit lifecycle/audit decisions and active step/stage binding dispositions; numeric budgets are no longer authoritative control state.
93
93
 
94
94
  Other topologies use their own applicable mechanisms: MAY-level loop-awareness planning hints, regular verification stop-gates, action gates, and task-specific skills. Mechanism coverage is not assumed identical across hosts.
95
95
 
@@ -7,7 +7,7 @@ Harness deliberately separates **semantic obligation strength** from **mechanica
7
7
  | Mechanism / obligation | Semantic strength | Runtime behavior | Hard block? |
8
8
  |---|---|---|---|
9
9
  | Workflow routing / selected topology | **MUST** resolve required obligations | Observe/remind about unresolved lifecycle evidence | No |
10
- | Suggested skill applicability | **MUST** evaluate; applicable core contract **MUST** run | Kernel/instructions expose dispositions | No |
10
+ | Active required skill/reference binding | **MUST** load/resolve before its step passes; applicable skill core contract **MUST** be followed | Step/stage contract records declared binding dispositions; active paths only | No |
11
11
  | Iteration/revision/replan/worker numbers | **MAY** guide planning | Planning hints only | No |
12
12
  | Verification before claim | **MUST** | Stop/hooks remind when evidence is missing | No |
13
13
  | Boundary guard / huge-noisy context | **SHOULD** narrow reads/searches | Warn | No |
@@ -4,7 +4,7 @@ Harness coordinates independently useful skills through a small workflow runtime
4
4
 
5
5
  The preferred shape is:
6
6
 
7
- > **guidance-first workflow + local skill autonomy + narrow runtime mechanisms**
7
+ > **step-scoped knowledge bindings + local skill autonomy + narrow runtime mechanisms**
8
8
 
9
9
  Platform-specific enforcement claims remain bounded by [platform-capabilities.md](platform-capabilities.md).
10
10
 
@@ -19,7 +19,7 @@ A skill should answer:
19
19
  3. **What workflow/mechanism do I expose?** A local flow, verifier, script, gate, or stable result when useful.
20
20
  4. **What evidence means I am done?** A check, artifact, or explicit blocked state.
21
21
 
22
- The router may surface several skills. Those suggestions must be evaluated from their actual `SKILL.md` flows before omission. They are **not** automatically all executed. Separately, the router selects one smallest sufficient outer topology. **The selected topology guides the run.**
22
+ The router emits normalized keyword/domain signals for requirement composition; these signals are not document selectors. Ordered requirement steps declare the skills/references they need, and only the active step or dependency-ready Fable stage exposes those paths. Separately, the router selects one smallest sufficient outer topology. **The selected topology governs the run.**
23
23
 
24
24
  ## Why Not One Global Skill Sequence?
25
25
 
@@ -33,16 +33,15 @@ A universal TODO/TDD/Fable sequence would:
33
33
  So Harness separates **topology** from **capabilities/domain skills**:
34
34
 
35
35
  ```text
36
- tier / task shape
37
36
  +
38
37
  selected execution topology
39
38
  +
40
- applicable skills/capabilities
39
+ active-step knowledge bindings
41
40
  +
42
41
  required invariants/gates
43
42
  ```
44
43
 
45
- The topology is the lifecycle contract. Skills are resolved for applicability within/around that contract.
44
+ The topology is the lifecycle contract. Skills and references are bound to the requirement steps that consume them.
46
45
 
47
46
  ## Mechanisms Over Prose
48
47
 
@@ -56,8 +55,9 @@ tier: 2
56
55
  strategy: iterative-single
57
56
  workflow_state: active
58
57
  required: scope-lock, objective-verification, loop-awareness
59
- suggest: tdd, verification-loop
60
- suggestion_policy: evaluate-applicability
58
+ knowledge_signals: test-related
59
+ active_step: behavior-change
60
+ required_bindings: tdd/SKILL.md
61
61
  escape_policy: workflow-uncovered-scope-only
62
62
  ```
63
63
 
@@ -81,18 +81,19 @@ Every skill should retain:
81
81
  - explicit neighbor links rather than hidden dependencies;
82
82
  - blocked/exit discipline.
83
83
 
84
- Read-before-omission exists so the agent can inspect this local contract without loading the entire ecosystem. If the skill flow applies, follow it; reading a skill is not permission to discard an applicable discipline because the model believes it already knows the answer.
84
+ An active required binding exists so the agent can inspect the local contract without loading the entire ecosystem. If the skill applies to that step, follow its core contract; later or unrelated bindings remain undisclosed.
85
85
 
86
86
  ## Coordination Patterns
87
87
 
88
88
  ### Router Decision
89
89
 
90
- `harness-everything/scripts/kernel-router.js` emits tier, strategy, invariants, skill suggestions, and an execution contract.
90
+ `harness-everything/scripts/kernel-router.js` emits tier, strategy, invariants, normalized knowledge signals, and an execution contract.
91
91
 
92
- - Suggested skills: read and resolve applicability (`use`, `not-applicable`, `unresolved/unavailable`).
92
+ - Knowledge signals: planning inputs only; they never choose paths.
93
+ - Active bindings: declare required/optional skill/reference ids and paths on ordered steps; future-step bindings remain hidden.
93
94
  - Selected topology: execute to resolution (`active` → verified/satisfied, blocked, or explicit evidence-backed escape).
94
95
 
95
- Names/descriptions/router summaries or “routine task” judgements cannot resolve applicability by themselves.
96
+ Required bindings must resolve before the current step/stage passes. Unknown/unavailable bindings stay visible; hooks and Stop remain fail-open.
96
97
 
97
98
  ### Fable Stage Contracts
98
99
 
@@ -28,7 +28,7 @@ flowchart TD
28
28
  X[Stop hook] --> V
29
29
  ```
30
30
 
31
- The local OpenAI adapter enforces cross-cutting invariants through the lifecycle events that it packages. Suggested skills require applicability evaluation; the selected topology is mandatory with scoped, evidence-backed escape. The [workflow runtime](workflow-runtime.md) documents pending/running/satisfied/blocked state, Fable entry, worktree isolation, completion evidence, and degradation limits. Tool enforcement is limited to the host tool names declared in `hooks/hooks.json`.
31
+ The local OpenAI adapter observes cross-cutting invariants through the lifecycle events that it packages. Required bindings are declared per active step/stage; the selected topology is mandatory with scoped, evidence-backed escape. The [workflow runtime](workflow-runtime.md) documents active binding visibility, pending/running/satisfied/blocked state, Fable entry, worktree isolation, completion evidence, and degradation limits. Tool enforcement is limited to the host tool names declared in `hooks/hooks.json`.
32
32
 
33
33
  ## Package layout
34
34
 
@@ -10,7 +10,7 @@ The mechanism/enforcement boundary remains platform-specific; see [Platform Capa
10
10
 
11
11
  1. **Reasoning autonomy remains high.** The model chooses implementation technique, tools, decomposition details, and tactics inside the selected workflow.
12
12
  2. **Selected lifecycle obligations are contracts, not advice.** Once a topology is selected, its required invariants, stages, checks, synthesis barriers, and verification obligations are semantic MUSTs. The model remains free to decide how to satisfy them.
13
- 3. **Skill applicability is explicit.** Every router-suggested skill MUST be read/evaluated before omission. If its real flow is applicable, its core contract MUST be followed; if it is not applicable, the agent MUST keep a flow-grounded reason.
13
+ 3. **Knowledge is step-scoped.** Normalized keyword signals help requirement composition but do not select documents. Ordered steps declare required/optional skill or reference bindings; active required bindings MUST be read and their applicable core contracts followed before the step passes.
14
14
  4. **Escape is conditional, not default.** If the selected workflow genuinely cannot represent part of the task, record the uncovered scope and evidence, then allow model-defined handling only for that uncovered portion.
15
15
  5. **Mechanisms do not define obligation strength.** Hooks/plugins may observe, remind, or mechanically block where the host supports them, but a missing blocking mechanism does not downgrade a semantic MUST to optional advice.
16
16
 
@@ -26,7 +26,7 @@ Harness uses three semantic strengths. Recommendation-style language is the last
26
26
 
27
27
  | Strength | Meaning | Typical use |
28
28
  |---|---|---|
29
- | **MUST** | Skipping the obligation violates the Harness semantic contract. Host enforcement may still be reminder-only. | routing, applicability resolution, applicable skill core contracts, selected-topology obligations, verification-before-claim, status, scope/safety/authorization evidence |
29
+ | **MUST** | Skipping the obligation violates the Harness semantic contract. Host enforcement may still be reminder-only. | routing, active required binding resolution, applicable skill core contracts, selected-topology obligations, verification-before-claim, status, scope/safety/authorization evidence |
30
30
  | **SHOULD** | The default is expected, but a concrete evidence-based exception is legitimate. | ordinary isolation, targeted editing, splitting mixed commit concerns, context-size/noisy-search discipline |
31
31
  | **MAY** | Optional optimization/capability. Omitting it needs no exception unless another contract selected it. | numeric planning hints, optional ensemble selection, extra deep references, opportunistic parallelism |
32
32
 
@@ -37,8 +37,9 @@ The repository-wide classification for #217 is:
37
37
  | Obligation | Strength |
38
38
  |---|---|
39
39
  | Route before software/project execution | **MUST** |
40
- | Read/evaluate every suggested skill before omission | **MUST** |
41
- | Follow an applicable suggested skill's core contract; otherwise keep a flow-grounded not-applicable reason | **MUST** |
40
+ | Resolve active required skill/reference bindings before their step | **MUST** |
41
+ | Follow an active skill binding's applicable core contract | **MUST** |
42
+ | Load unrelated keyword-matched documents or future-step bindings | Not required |
42
43
  | Resolve selected-topology invariants/stages/checks/synthesis/verification | **MUST** |
43
44
  | Choose tools, implementation technique, decomposition details, and local tactics | **MAY** within the MUST contract |
44
45
  | Verify before claiming completion | **MUST** |
@@ -67,7 +68,7 @@ The router chooses the **smallest sufficient topology**:
67
68
  - `fable-parallel` only for validated independent workstreams;
68
69
  - `fable-multi-agent-workspace` when persistent roles/handoffs/memory are materially needed.
69
70
 
70
- Domain skills remain conditional on applicability. The contract says “execute the selected lifecycle”, not “execute every skill in the repository.”
71
+ Domain skills and references are conditional on the needs of each requirement step. The contract says “execute the selected lifecycle”, not “execute every skill in the repository.” Unknown required bindings remain visible and unresolved; they do not turn workflow hooks into a persistent lock.
71
72
 
72
73
  ## The 4 Pillars of AI Model Guidance
73
74
 
@@ -81,7 +82,7 @@ Circuit breakers, action gates, workflow gates, scope guards, and completion gat
81
82
 
82
83
  ### 3. Context Preservation — Progressive Disclosure
83
84
 
84
- **SHOULD** load the smallest useful context. A suggested skill's complete entry **MUST** be read before deciding applicability; optional deep references **MAY** remain unloaded unless needed. Machine-visible workflow state SHOULD carry lifecycle facts instead of repeating large prompt blocks.
85
+ **SHOULD** load the smallest useful context. An active required skill's complete entry **MUST** be read and followed; optional bindings resolve as loaded or not-needed. Future-step bindings remain undisclosed until active. Machine-visible workflow state SHOULD carry lifecycle facts instead of repeating large prompt blocks.
85
86
 
86
87
  ### 4. Self-Evolution — Learn from Workflow Evidence
87
88