Skip to content

Commit 9796311

Browse files
committed
feat: expose readiness promotion blockers
1 parent 5c40ad0 commit 9796311

15 files changed

Lines changed: 267 additions & 4 deletions

docs/brainstorms/2026-04-19-mainline-progress-reconciliation-and-post-m7-direction-requirements.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ This document re-baselines current mainline truth on 2026-04-19, records what ch
204204
- readiness now reports `graphBackendIndependent`
205205
- integrated readiness now requires independent graph-backend evidence in addition to independent vector evidence
206206
- overall status still remains `in_progress` / `no-go`, because current mainline persistence is still file-backed snapshot-only.
207+
- The next bounded execution slice after that makes no-go reasons machine-readable:
208+
- readiness now reports `promotionBlockers`
209+
- current mainline blockers are `graph_backend_not_independent` and `vector_backend_not_independent`
210+
- missing query/vector boundary cases now return distinct blocker ids instead of only prose recommendations
211+
- overall status still remains `in_progress` / `no-go`.
207212
- Current expected outcome on `main` remains:
208213
- status `in_progress`
209214
- decision `no-go`

docs/diataxis/en/explanation/development-progress-dashboard.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,26 @@ Execution anchor:
297297
- decision stays `no-go`,
298298
- file snapshots still do not qualify as independent graph backend readiness.
299299

300+
## Latest Mainline Increment (2026-04-20 M8.11 Foundation Promotion-Blocker Evidence)
301+
302+
- Added machine-readable readiness blockers so operator tooling no longer has to parse prose recommendations:
303+
- `FoundationReadinessReport.promotionBlockers`
304+
- typed blocker ids now distinguish graph, query, vector, docs, store, and verifier failures.
305+
- Current mainline blocker set is now explicit:
306+
- `graph_backend_not_independent`
307+
- `vector_backend_not_independent`
308+
- Missing-boundary fixtures are also distinguishable now:
309+
- `query_backend_boundary_missing`
310+
- `vector_backend_missing`
311+
- Runtime/operator visibility stayed aligned:
312+
- `scripts/verify-foundation-readiness.js` prints promotion blockers,
313+
- markdown readiness reports include a dedicated `Promotion Blockers` section,
314+
- `src/frontend/agent_workspace_runtime.js` persists blocker ids in `lastFoundationReadiness` and surfaces them in the toolbar summary.
315+
- Guardrail remains unchanged on `main`:
316+
- status stays `in_progress`,
317+
- decision stays `no-go`,
318+
- blockers are now explicit evidence, not a status change.
319+
300320
## Latest Mainline Increment (2026-04-16 M7.2 Runtime Evidence Deepening Lane)
301321

302322
- Expanded runtime behavior coverage in `src/agent_workspace.runtime.behavior.test.ts` for pane lifecycle evidence:

docs/diataxis/en/explanation/foundation-reentry-readiness-checklist.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Bundle note:
5757
- decision `No-Go`
5858
- rationale: mainline persistence is still `file_snapshot_only`, query-backend and vector-boundary modules are present, but graph-backend independence is absent and the vector adapter is still only `prefilter_only`, so independent graph/ANN evidence is still absent.
5959
- The readiness payload now makes that distinction explicit:
60+
- `promotionBlockers`
6061
- `graphBackendStatus`
6162
- `graphBackendSignalKind`
6263
- `graphBackendIndependent`
@@ -70,6 +71,9 @@ Bundle note:
7071
- `graphBackendStatus = file_snapshot_only`
7172
- `graphBackendSignalKind = json_snapshot_file`
7273
- `graphBackendIndependent = false`
74+
- Current expected promotion blockers on `main` are:
75+
- `graph_backend_not_independent`
76+
- `vector_backend_not_independent`
7377
- Current expected signal semantics on `main` are:
7478
- `queryBackendScoreSignals = [keyword_matches, title_match_bonus, vector_prefilter_content_bonus, relation_bonus]`
7579
- `vectorAdapterSignalKind = lexical_token_overlap`

docs/diataxis/zh/explanation/development-progress-dashboard.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,26 @@
297297
- 决策仍为 `no-go`
298298
- file snapshot 仍不构成独立 graph backend readiness。
299299

300+
## 主线最新增量(2026-04-20 M8.11 Foundation 晋级阻塞项证据)
301+
302+
- 已新增 machine-readable readiness blockers,执行侧不再需要解析自由文本 recommendation 才能知道 `no-go` 原因:
303+
- `FoundationReadinessReport.promotionBlockers`
304+
- typed blocker id 现在可区分 graph、query、vector、docs、store、verifier 类失败。
305+
- 当前主线阻塞集合现在是显式事实:
306+
- `graph_backend_not_independent`
307+
- `vector_backend_not_independent`
308+
- 缺失边界的 fixture 现在也能被准确区分:
309+
- `query_backend_boundary_missing`
310+
- `vector_backend_missing`
311+
- 运行时/执行侧可见性保持对齐:
312+
- `scripts/verify-foundation-readiness.js` 现在输出 promotion blockers,
313+
- readiness markdown 报告新增独立 `Promotion Blockers` 小节,
314+
- `src/frontend/agent_workspace_runtime.js` 会在 `lastFoundationReadiness` 中保留 blocker ids,并在工具栏摘要里显示。
315+
- `main` 上的护栏结论保持不变:
316+
- 状态仍为 `in_progress`
317+
- 决策仍为 `no-go`
318+
- blocker 只是把阻塞证据显式化,不构成状态升级。
319+
300320
## 主线最新增量(2026-04-16 M7.2 运行时证据加深链路)
301321

302322
- 已在 `src/agent_workspace.runtime.behavior.test.ts` 扩展 pane 生命周期证据覆盖:

docs/diataxis/zh/explanation/foundation-reentry-readiness-checklist.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
- 决策 `No-Go`
5858
- 原因:主线 persistence 仍是 `file_snapshot_only`,query-backend 与 vector 边界虽已进入主线,但 graph backend 独立性仍缺失,且 vector adapter 仍只是 `prefilter_only`,因此独立 graph/ANN 证据仍未进入主线。
5959
- readiness 载荷现在也显式区分这层语义:
60+
- `promotionBlockers`
6061
- `graphBackendStatus`
6162
- `graphBackendSignalKind`
6263
- `graphBackendIndependent`
@@ -70,6 +71,9 @@
7071
- `graphBackendStatus = file_snapshot_only`
7172
- `graphBackendSignalKind = json_snapshot_file`
7273
- `graphBackendIndependent = false`
74+
- 当前 `main` 的预期晋级阻塞项为:
75+
- `graph_backend_not_independent`
76+
- `vector_backend_not_independent`
7377
- 当前 `main` 的预期信号语义为:
7478
- `queryBackendScoreSignals = [keyword_matches, title_match_bonus, vector_prefilter_content_bonus, relation_bonus]`
7579
- `vectorAdapterSignalKind = lexical_token_overlap`

scripts/foundation-readiness-utils.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ function fileExists(filePath) {
2525
}
2626
}
2727

28+
function createPromotionBlocker(blockerId, summary) {
29+
return { blockerId, summary };
30+
}
31+
2832
function resolveVectorAdapterStatus(vectorAdapterSource, vectorAdapterModulePresent) {
2933
if (!vectorAdapterModulePresent) {
3034
return 'absent';
@@ -268,6 +272,7 @@ function evaluateFoundationReadiness(options = {}) {
268272
},
269273
];
270274

275+
const promotionBlockers = [];
271276
const recommendations = [];
272277

273278
if (!fileBackedStore && !graphBackendIndependent) {
@@ -277,42 +282,96 @@ function evaluateFoundationReadiness(options = {}) {
277282
}
278283

279284
if (!storeModulePresent) {
285+
promotionBlockers.push(
286+
createPromotionBlocker(
287+
'store_backend_evidence_missing',
288+
'Store backend evidence is missing or unreadable.'
289+
)
290+
);
280291
recommendations.push(
281292
'Restore explicit store backend evidence before advancing foundation readiness claims.'
282293
);
283294
}
284295

285296
if (!graphBackendIndependent) {
297+
promotionBlockers.push(
298+
createPromotionBlocker(
299+
'graph_backend_not_independent',
300+
'Graph backend still resolves to file-backed snapshot semantics.'
301+
)
302+
);
286303
recommendations.push(
287304
'Keep foundation re-entry at no-go while graph persistence remains file-backed snapshot storage without independent graph backend semantics on mainline.'
288305
);
289306
}
290307

291308
if (!graphAdapterModulePresent && !vectorAdapterModulePresent) {
309+
promotionBlockers.push(
310+
createPromotionBlocker(
311+
'query_backend_boundary_missing',
312+
'Dedicated query backend boundary is absent.'
313+
)
314+
);
315+
promotionBlockers.push(
316+
createPromotionBlocker(
317+
'vector_backend_missing',
318+
'Dedicated vector adapter boundary is absent.'
319+
)
320+
);
292321
recommendations.push(
293322
'Do not claim foundation re-entry integrated while dedicated graph/vector adapter modules remain absent on mainline.'
294323
);
295324
} else if (!graphAdapterModulePresent) {
325+
promotionBlockers.push(
326+
createPromotionBlocker(
327+
'query_backend_boundary_missing',
328+
'Dedicated query backend boundary is absent.'
329+
)
330+
);
296331
recommendations.push(
297332
'Keep foundation re-entry at no-go while a dedicated query backend boundary is still absent on mainline.'
298333
);
299334
} else if (!vectorAdapterModulePresent) {
335+
promotionBlockers.push(
336+
createPromotionBlocker(
337+
'vector_backend_missing',
338+
'Dedicated vector adapter boundary is absent.'
339+
)
340+
);
300341
recommendations.push(
301342
'Keep foundation re-entry at no-go while vector adapter independence remains absent on mainline.'
302343
);
303344
} else if (!vectorAdapterIndependent) {
345+
promotionBlockers.push(
346+
createPromotionBlocker(
347+
'vector_backend_not_independent',
348+
'Vector backend still resolves to lexical prefilter semantics.'
349+
)
350+
);
304351
recommendations.push(
305352
'Keep foundation re-entry at no-go while the vector adapter remains a local prefilter boundary without independent ANN evidence on mainline.'
306353
);
307354
}
308355

309356
if (!checklistPagesPresent || !dashboardReferencesPresent) {
357+
promotionBlockers.push(
358+
createPromotionBlocker(
359+
'docs_not_aligned',
360+
'EN/ZH checklist and dashboard references are not aligned.'
361+
)
362+
);
310363
recommendations.push(
311364
'Keep EN/ZH checklist and dashboard references aligned before treating foundation work as active on mainline.'
312365
);
313366
}
314367

315368
if (!readinessVerifierPresent) {
369+
promotionBlockers.push(
370+
createPromotionBlocker(
371+
'readiness_verifier_missing',
372+
'Readiness verifier command is missing from package scripts.'
373+
)
374+
);
316375
recommendations.push(
317376
'Expose a single readiness verifier command so operators can inspect current mainline baseline without scanning docs manually.'
318377
);
@@ -353,6 +412,7 @@ function evaluateFoundationReadiness(options = {}) {
353412
readinessVerifierPresent,
354413
},
355414
mandatoryChecks,
415+
promotionBlockers,
356416
recommendations,
357417
};
358418
}
@@ -400,6 +460,15 @@ function formatFoundationReadinessMarkdown(result, reportPaths) {
400460
lines.push(`- ${entry.gateId}: \`${entry.command}\``);
401461
});
402462

463+
lines.push('', '## Promotion Blockers', '');
464+
if (result.promotionBlockers.length === 0) {
465+
lines.push('- none');
466+
} else {
467+
result.promotionBlockers.forEach((entry) => {
468+
lines.push(`- ${entry.blockerId}: ${entry.summary}`);
469+
});
470+
}
471+
403472
lines.push('', '## Recommendations', '');
404473
result.recommendations.forEach((entry) => {
405474
lines.push(`- ${entry}`);

scripts/verify-foundation-readiness.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ function printHumanReport(result, reportPaths) {
5353
result.mandatoryChecks.forEach((entry) => {
5454
console.log(` - ${entry.gateId}: ${entry.command}`);
5555
});
56+
console.log('[Foundation Readiness] Promotion blockers:');
57+
if (result.promotionBlockers.length === 0) {
58+
console.log(' - none');
59+
} else {
60+
result.promotionBlockers.forEach((entry) => {
61+
console.log(` - ${entry.blockerId}: ${entry.summary}`);
62+
});
63+
}
5664
console.log('[Foundation Readiness] Recommendations:');
5765
result.recommendations.forEach((entry) => {
5866
console.log(` - ${entry}`);

src/agent_workspace.runtime.behavior.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1694,7 +1694,18 @@ describe('agent workspace runtime behavior', () => {
16941694
command: 'npm test -- src/knowledge.api.contract.test.ts --runInBand',
16951695
},
16961696
],
1697+
promotionBlockers: [
1698+
{
1699+
blockerId: 'graph_backend_not_independent',
1700+
summary: 'Graph backend still resolves to file-backed snapshot semantics.',
1701+
},
1702+
{
1703+
blockerId: 'vector_backend_not_independent',
1704+
summary: 'Vector backend still resolves to lexical prefilter semantics.',
1705+
},
1706+
],
16971707
recommendations: [
1708+
'Keep foundation re-entry at no-go while graph persistence remains file-backed snapshot storage without independent graph backend semantics on mainline.',
16981709
'Keep foundation re-entry at no-go while the vector adapter remains a local prefilter boundary without independent ANN evidence on mainline.',
16991710
],
17001711
},
@@ -1723,6 +1734,8 @@ describe('agent workspace runtime behavior', () => {
17231734
expect(messages.textContent || '').toContain('prefilter_only');
17241735
expect(messages.textContent || '').toContain('local_hybrid');
17251736
expect(messages.textContent || '').toContain('lexical_token_overlap');
1737+
expect(messages.textContent || '').toContain('graph_backend_not_independent');
1738+
expect(messages.textContent || '').toContain('vector_backend_not_independent');
17261739

17271740
const snapshot = runtime.getDiagnosticsSnapshot();
17281741
expect(snapshot.lastFoundationReadiness).toEqual(
@@ -1744,7 +1757,12 @@ describe('agent workspace runtime behavior', () => {
17441757
vectorAdapterSignalKind: 'lexical_token_overlap',
17451758
vectorAdapterIndependent: false,
17461759
vectorAdapterLinkedIntoQueryBackend: true,
1747-
recommendationsCount: 1,
1760+
promotionBlockerIds: [
1761+
'graph_backend_not_independent',
1762+
'vector_backend_not_independent',
1763+
],
1764+
promotionBlockersCount: 2,
1765+
recommendationsCount: 2,
17481766
})
17491767
);
17501768
});

src/foundation.readiness.contract.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ type FoundationReadinessResult = {
3333
packageScripts: {
3434
readinessVerifierPresent: boolean;
3535
};
36+
promotionBlockers: Array<{
37+
blockerId:
38+
| 'store_backend_evidence_missing'
39+
| 'graph_backend_not_independent'
40+
| 'query_backend_boundary_missing'
41+
| 'vector_backend_missing'
42+
| 'vector_backend_not_independent'
43+
| 'docs_not_aligned'
44+
| 'readiness_verifier_missing';
45+
summary: string;
46+
}>;
3647
recommendations: string[];
3748
};
3849

@@ -133,6 +144,10 @@ describe('foundation readiness contract', () => {
133144
expect(result.documents.checklistPagesPresent).toBe(true);
134145
expect(result.documents.dashboardReferencesPresent).toBe(true);
135146
expect(result.packageScripts.readinessVerifierPresent).toBe(true);
147+
expect(result.promotionBlockers.map((entry) => entry.blockerId)).toEqual([
148+
'graph_backend_not_independent',
149+
'vector_backend_not_independent',
150+
]);
136151
expect(result.recommendations).toContain(
137152
'Keep foundation re-entry at no-go while graph persistence remains file-backed snapshot storage without independent graph backend semantics on mainline.'
138153
);
@@ -200,6 +215,9 @@ describe('foundation readiness contract', () => {
200215
expect(result.baseline.vectorAdapterLinkedIntoQueryBackend).toBe(false);
201216
expect(result.documents.checklistPagesPresent).toBe(true);
202217
expect(result.documents.dashboardReferencesPresent).toBe(true);
218+
expect(result.promotionBlockers.map((entry) => entry.blockerId)).toEqual([
219+
'graph_backend_not_independent',
220+
]);
203221
expect(result.recommendations).toContain(
204222
'Keep foundation re-entry at no-go while graph persistence remains file-backed snapshot storage without independent graph backend semantics on mainline.'
205223
);
@@ -264,5 +282,54 @@ describe('foundation readiness contract', () => {
264282
expect(result.baseline.vectorAdapterLinkedIntoQueryBackend).toBe(false);
265283
expect(result.documents.checklistPagesPresent).toBe(true);
266284
expect(result.documents.dashboardReferencesPresent).toBe(true);
285+
expect(result.promotionBlockers).toEqual([]);
286+
});
287+
288+
test('missing query or vector boundaries produce machine-readable blockers', () => {
289+
expect(utils).not.toBeNull();
290+
const fakeRepo = temp.child('repo-missing-boundaries');
291+
292+
temp.file(
293+
'repo-missing-boundaries/package.json',
294+
JSON.stringify(
295+
{
296+
scripts: {
297+
'verify:foundation:readiness': 'node scripts/verify-foundation-readiness.js',
298+
},
299+
},
300+
null,
301+
2
302+
)
303+
);
304+
temp.file(
305+
'repo-missing-boundaries/src/learning/store.ts',
306+
"export async function loadSnapshot() {}\nexport function createFileBackedKnowledgeGraphStore() {}\nexport const diagnostics = { storeType: 'file' };\n"
307+
);
308+
temp.file(
309+
'repo-missing-boundaries/docs/diataxis/en/explanation/foundation-reentry-readiness-checklist.md',
310+
'foundation\n'
311+
);
312+
temp.file(
313+
'repo-missing-boundaries/docs/diataxis/zh/explanation/foundation-reentry-readiness-checklist.md',
314+
'foundation\n'
315+
);
316+
temp.file(
317+
'repo-missing-boundaries/docs/diataxis/en/explanation/development-progress-dashboard.md',
318+
'foundation-reentry-readiness-checklist\n'
319+
);
320+
temp.file(
321+
'repo-missing-boundaries/docs/diataxis/zh/explanation/development-progress-dashboard.md',
322+
'foundation-reentry-readiness-checklist\n'
323+
);
324+
325+
const result = (utils as FoundationReadinessModule).evaluateFoundationReadiness({
326+
repoRoot: fakeRepo,
327+
});
328+
329+
expect(result.promotionBlockers.map((entry) => entry.blockerId)).toEqual([
330+
'graph_backend_not_independent',
331+
'query_backend_boundary_missing',
332+
'vector_backend_missing',
333+
]);
267334
});
268335
});

0 commit comments

Comments
 (0)