agentic-workflow-manager 9.8.0 → 9.9.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.
- package/dist/src/commands/job/gate.js +18 -0
- package/dist/src/commands/job/index.js +61 -0
- package/dist/src/commands/model-policy/index.js +73 -0
- package/dist/src/commands/plan/index.js +59 -2
- package/dist/src/commands/watch/apply.js +32 -0
- package/dist/src/core/admission/index.js +52 -2
- package/dist/src/core/admission/registry-contracts.js +1 -1
- package/dist/src/core/journal/requests.js +1 -1
- package/dist/src/core/journal/types.js +22 -1
- package/dist/src/core/model-policy/canonical.js +18 -0
- package/dist/src/core/model-policy/capabilities.js +197 -0
- package/dist/src/core/model-policy/capability-types.js +2 -0
- package/dist/src/core/model-policy/journal.js +124 -0
- package/dist/src/core/model-policy/paths.js +12 -0
- package/dist/src/core/model-policy/resolve.js +116 -0
- package/dist/src/core/model-policy/store.js +182 -0
- package/dist/src/core/model-policy/types.js +2 -0
- package/dist/src/core/model-policy/validate.js +112 -0
- package/dist/src/core/plan/identity.js +4 -2
- package/dist/src/core/plan/validate.js +15 -16
- package/dist/src/index.js +2 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +16 -0
- package/dist/tests/commands/job/gate.test.js +9 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +46 -0
- package/dist/tests/commands/job/routing.test.js +11 -0
- package/dist/tests/commands/model-policy/index.test.js +57 -0
- package/dist/tests/commands/plan/index.test.js +52 -0
- package/dist/tests/commands/watch/apply.test.js +12 -0
- package/dist/tests/core/admission/index.test.js +53 -0
- package/dist/tests/core/model-policy/capabilities.test.js +365 -0
- package/dist/tests/core/model-policy/journal.test.js +46 -0
- package/dist/tests/core/model-policy/policy.test.js +47 -0
- package/dist/tests/core/model-policy/resolve.test.js +111 -0
- package/dist/tests/core/model-policy/store.test.js +177 -0
- package/dist/tests/core/plan/validate.test.js +40 -2
- package/dist/tests/core/secure-fs/native-bridge.test.js +81 -4
- package/dist/tests/structural/directory-fsync-is-single-source.test.js +97 -0
- package/dist/tests/structural/native-release-artifacts.test.js +14 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/linux-arm64/secure_fs.node +0 -0
- package/prebuilds/linux-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_IDENTIFIER = exports.MAX_MAPPINGS = exports.MAX_POLICY_BYTES = void 0;
|
|
4
|
+
exports.canonicalUtcTimestamp = canonicalUtcTimestamp;
|
|
5
|
+
exports.validatePolicyContent = validatePolicyContent;
|
|
6
|
+
exports.validateApprovedPolicy = validateApprovedPolicy;
|
|
7
|
+
exports.assertDigest = assertDigest;
|
|
8
|
+
const providers_1 = require("../../providers");
|
|
9
|
+
exports.MAX_POLICY_BYTES = 256 * 1024;
|
|
10
|
+
exports.MAX_MAPPINGS = 64;
|
|
11
|
+
exports.MAX_IDENTIFIER = 128;
|
|
12
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
13
|
+
const RUNTIME_KIND = /^[a-z0-9][a-z0-9_-]{0,127}$/;
|
|
14
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/;
|
|
15
|
+
const CANONICAL_UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
16
|
+
const profiles = ['mechanical', 'integration', 'judgment'];
|
|
17
|
+
function record(value, label) {
|
|
18
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
19
|
+
throw new Error(`${label} must be an object`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function exact(object, keys, label) {
|
|
23
|
+
const actual = Object.keys(object);
|
|
24
|
+
if (actual.length !== keys.length || actual.some(key => !keys.includes(key)))
|
|
25
|
+
throw new Error(`${label} has unknown or missing fields`);
|
|
26
|
+
}
|
|
27
|
+
function text(value, label, pattern) {
|
|
28
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > exports.MAX_IDENTIFIER || CONTROL.test(value) || (pattern && !pattern.test(value)))
|
|
29
|
+
throw new Error(`${label} is invalid`);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function canonicalUtcTimestamp(value, label) {
|
|
33
|
+
if (typeof value !== 'string' || !CANONICAL_UTC_TIMESTAMP.test(value))
|
|
34
|
+
throw new Error(`${label} must be a canonical UTC ISO-8601 timestamp`);
|
|
35
|
+
const parsed = new Date(value);
|
|
36
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value)
|
|
37
|
+
throw new Error(`${label} must be a canonical UTC ISO-8601 timestamp`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function bool(value, label) { if (typeof value !== 'boolean')
|
|
41
|
+
throw new Error(`${label} must be boolean`); return value; }
|
|
42
|
+
function agent(value) { if (typeof value !== 'string' || !providers_1.AGENT_TARGETS.includes(value))
|
|
43
|
+
throw new Error('mapping target is invalid'); return value; }
|
|
44
|
+
function selection(value, label) {
|
|
45
|
+
const item = record(value, label);
|
|
46
|
+
exact(item, ['selector', 'effort'], label);
|
|
47
|
+
const selector = record(item.selector, `${label}.selector`);
|
|
48
|
+
exact(selector, ['kind', 'id'], `${label}.selector`);
|
|
49
|
+
if (selector.kind !== 'model' && selector.kind !== 'tier')
|
|
50
|
+
throw new Error(`${label}.selector.kind is invalid`);
|
|
51
|
+
const effort = record(item.effort, `${label}.effort`);
|
|
52
|
+
if (effort.kind === 'explicit') {
|
|
53
|
+
exact(effort, ['kind', 'value'], `${label}.effort`);
|
|
54
|
+
return { selector: { kind: selector.kind, id: text(selector.id, `${label}.selector.id`) }, effort: { kind: 'explicit', value: text(effort.value, `${label}.effort.value`) } };
|
|
55
|
+
}
|
|
56
|
+
if (effort.kind === 'runtime-default') {
|
|
57
|
+
exact(effort, ['kind'], `${label}.effort`);
|
|
58
|
+
return { selector: { kind: selector.kind, id: text(selector.id, `${label}.selector.id`) }, effort: { kind: 'runtime-default' } };
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`${label}.effort.kind is invalid`);
|
|
61
|
+
}
|
|
62
|
+
function mapping(value) {
|
|
63
|
+
const item = record(value, 'mapping');
|
|
64
|
+
exact(item, ['target', 'runtimeKind', 'profiles', 'fullCapability', 'degradation'], 'mapping');
|
|
65
|
+
const rawProfiles = record(item.profiles, 'mapping.profiles');
|
|
66
|
+
exact(rawProfiles, profiles, 'mapping.profiles');
|
|
67
|
+
const degradation = record(item.degradation, 'mapping.degradation');
|
|
68
|
+
exact(degradation, ['allowMissingModelOverride', 'allowMissingEffortOverride', 'allowMissingObservedIdentity'], 'mapping.degradation');
|
|
69
|
+
return { target: agent(item.target), runtimeKind: text(item.runtimeKind, 'mapping.runtimeKind', RUNTIME_KIND), profiles: {
|
|
70
|
+
mechanical: selection(rawProfiles.mechanical, 'mapping.profiles.mechanical'), integration: selection(rawProfiles.integration, 'mapping.profiles.integration'), judgment: selection(rawProfiles.judgment, 'mapping.profiles.judgment'),
|
|
71
|
+
}, fullCapability: selection(item.fullCapability, 'mapping.fullCapability'), degradation: {
|
|
72
|
+
allowMissingModelOverride: bool(degradation.allowMissingModelOverride, 'mapping.degradation.allowMissingModelOverride'), allowMissingEffortOverride: bool(degradation.allowMissingEffortOverride, 'mapping.degradation.allowMissingEffortOverride'), allowMissingObservedIdentity: bool(degradation.allowMissingObservedIdentity, 'mapping.degradation.allowMissingObservedIdentity'),
|
|
73
|
+
} };
|
|
74
|
+
}
|
|
75
|
+
function validatePolicyContent(value) {
|
|
76
|
+
const item = record(value, 'policy content');
|
|
77
|
+
exact(item, ['schema', 'mappings', 'implementationBudget'], 'policy content');
|
|
78
|
+
if (item.schema !== 'model-policy/v1')
|
|
79
|
+
throw new Error('policy schema is invalid');
|
|
80
|
+
if (!Array.isArray(item.mappings) || item.mappings.length > exports.MAX_MAPPINGS)
|
|
81
|
+
throw new Error('policy mappings are invalid');
|
|
82
|
+
const mappings = item.mappings.map(mapping);
|
|
83
|
+
const unique = new Set();
|
|
84
|
+
for (const row of mappings) {
|
|
85
|
+
const key = `${row.target}\0${row.runtimeKind}`;
|
|
86
|
+
if (unique.has(key))
|
|
87
|
+
throw new Error('duplicate policy target/runtime mapping');
|
|
88
|
+
unique.add(key);
|
|
89
|
+
}
|
|
90
|
+
const budget = record(item.implementationBudget, 'implementationBudget');
|
|
91
|
+
exact(budget, ['maxAttempts', 'escalation', 'judgmentEfforts'], 'implementationBudget');
|
|
92
|
+
if (budget.maxAttempts !== 3 || !Array.isArray(budget.escalation) || budget.escalation.length !== 3 || budget.escalation.join(',') !== 'mechanical,integration,judgment' || !Array.isArray(budget.judgmentEfforts) || budget.judgmentEfforts.join(',') !== 'medium,high')
|
|
93
|
+
throw new Error('implementation budget is invalid');
|
|
94
|
+
return { schema: 'model-policy/v1', mappings, implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } };
|
|
95
|
+
}
|
|
96
|
+
function validateApprovedPolicy(value) {
|
|
97
|
+
const item = record(value, 'approved policy');
|
|
98
|
+
exact(item, ['schema', 'content', 'contentDigest', 'approval', 'lineage'], 'approved policy');
|
|
99
|
+
if (item.schema !== 'approved-model-policy/v1')
|
|
100
|
+
throw new Error('approved policy schema is invalid');
|
|
101
|
+
const approval = record(item.approval, 'approval');
|
|
102
|
+
exact(approval, ['approvedAt', 'approvalId'], 'approval');
|
|
103
|
+
const lineage = record(item.lineage, 'lineage');
|
|
104
|
+
exact(lineage, ['previousDigest'], 'lineage');
|
|
105
|
+
const contentDigest = text(item.contentDigest, 'contentDigest', SHA256);
|
|
106
|
+
if (typeof approval.approvalId !== 'string' || approval.approvalId.length === 0 || approval.approvalId.length > exports.MAX_IDENTIFIER || CONTROL.test(approval.approvalId) || (lineage.previousDigest !== null && (typeof lineage.previousDigest !== 'string' || !SHA256.test(lineage.previousDigest))))
|
|
107
|
+
throw new Error('approved policy metadata is invalid');
|
|
108
|
+
const approvedAt = canonicalUtcTimestamp(approval.approvedAt, 'approval.approvedAt');
|
|
109
|
+
return { schema: 'approved-model-policy/v1', content: validatePolicyContent(item.content), contentDigest, approval: { approvedAt, approvalId: approval.approvalId }, lineage: { previousDigest: lineage.previousDigest } };
|
|
110
|
+
}
|
|
111
|
+
function assertDigest(value, label) { if (typeof value !== 'string' || !SHA256.test(value))
|
|
112
|
+
throw new Error(`${label} must be a 64-character lowercase SHA-256 digest`); }
|
|
@@ -12,6 +12,8 @@ exports.MAX_PLAN_SNAPSHOT_BYTES = 1024 * 1024;
|
|
|
12
12
|
exports.EXECUTION_IDENTITY_SCHEMA = 'awm-plan-execution/v1';
|
|
13
13
|
const START = '<!-- AWM:COMPACT-SLICES:START v1 -->';
|
|
14
14
|
const END = '<!-- AWM:COMPACT-SLICES:END v1 -->';
|
|
15
|
+
const START_V2 = '<!-- AWM:COMPACT-SLICES:START v2 -->';
|
|
16
|
+
const END_V2 = '<!-- AWM:COMPACT-SLICES:END v2 -->';
|
|
15
17
|
/** Only lifecycle syntax actually emitted by the skills is non-executable.
|
|
16
18
|
* Unknown metadata, prose, inline comments and code remain identity-bearing. */
|
|
17
19
|
function lifecycleMarker(line) {
|
|
@@ -53,11 +55,11 @@ function executionPlanDigest(text) {
|
|
|
53
55
|
}
|
|
54
56
|
if (fence)
|
|
55
57
|
return { line };
|
|
56
|
-
if (line === START) {
|
|
58
|
+
if (line === START || line === START_V2) {
|
|
57
59
|
manifest = true;
|
|
58
60
|
return { line };
|
|
59
61
|
}
|
|
60
|
-
if (line === END) {
|
|
62
|
+
if (line === END || line === END_V2) {
|
|
61
63
|
manifest = false;
|
|
62
64
|
return { line };
|
|
63
65
|
}
|
|
@@ -14,6 +14,8 @@ const json_1 = require("./json");
|
|
|
14
14
|
const identity_1 = require("./identity");
|
|
15
15
|
const START = '<!-- AWM:COMPACT-SLICES:START v1 -->';
|
|
16
16
|
const END = '<!-- AWM:COMPACT-SLICES:END v1 -->';
|
|
17
|
+
const START_V2 = '<!-- AWM:COMPACT-SLICES:START v2 -->';
|
|
18
|
+
const END_V2 = '<!-- AWM:COMPACT-SLICES:END v2 -->';
|
|
17
19
|
const MAX_PLAN = 1024 * 1024;
|
|
18
20
|
const MAX_MANIFEST = 256 * 1024;
|
|
19
21
|
const MAX_SOURCE = 1024 * 1024;
|
|
@@ -356,22 +358,18 @@ function validatePlan(planPath, cwd, snapshot) {
|
|
|
356
358
|
}
|
|
357
359
|
const starts = count(text, START);
|
|
358
360
|
const ends = count(text, END);
|
|
359
|
-
const signaled = starts > 0 || ends > 0 || compactSchemaSignal(text);
|
|
361
|
+
const signaled = starts > 0 || ends > 0 || count(text, START_V2) > 0 || count(text, END_V2) > 0 || compactSchemaSignal(text);
|
|
360
362
|
if (!signaled)
|
|
361
363
|
return markerlessSchemaClassification(text) ?? { state: 'migration-required', reason: 'unmarked-plan' };
|
|
362
|
-
|
|
364
|
+
const start = text.includes(START_V2) ? START_V2 : START;
|
|
365
|
+
const end = start === START_V2 ? END_V2 : END;
|
|
366
|
+
const startsForSchema = count(text, start);
|
|
367
|
+
const endsForSchema = count(text, end);
|
|
368
|
+
if (starts === 0 && ends === 0 && startsForSchema === 0 && endsForSchema === 0)
|
|
363
369
|
return markerlessSchemaClassification(text) ?? diagnostic('PLAN_MARKERS', 'compact markers must occur once in order');
|
|
364
|
-
if (
|
|
365
|
-
const partial = starts === 1 ? text.slice(text.indexOf(START) + START.length, ends ? text.indexOf(END) : undefined).trim() : '';
|
|
366
|
-
try {
|
|
367
|
-
const candidate = (0, json_1.parseJsonNoDuplicate)(partial);
|
|
368
|
-
if (object(candidate) && typeof candidate.schema === 'string' && Buffer.byteLength(candidate.schema, 'utf8') <= MAX_STRING && candidate.schema.startsWith('compact-slices/') && candidate.schema !== 'compact-slices/v1')
|
|
369
|
-
return unsupported(candidate.schema);
|
|
370
|
-
}
|
|
371
|
-
catch { /* invalid below */ }
|
|
370
|
+
if (startsForSchema !== 1 || endsForSchema !== 1 || text.indexOf(start) > text.indexOf(end) || (start === START && (count(text, START_V2) !== 0 || count(text, END_V2) !== 0)) || (start === START_V2 && (starts !== 0 || ends !== 0)))
|
|
372
371
|
return diagnostic('PLAN_MARKERS', 'compact markers must occur once in order');
|
|
373
|
-
|
|
374
|
-
const body = text.slice(text.indexOf(START) + START.length, text.indexOf(END));
|
|
372
|
+
const body = text.slice(text.indexOf(start) + start.length, text.indexOf(end));
|
|
375
373
|
if (Buffer.byteLength(body, 'utf8') > MAX_MANIFEST)
|
|
376
374
|
return diagnostic('PLAN_LIMIT', 'manifest exceeds maximum size');
|
|
377
375
|
let raw;
|
|
@@ -385,7 +383,7 @@ function validatePlan(planPath, cwd, snapshot) {
|
|
|
385
383
|
return diagnostic('PLAN_SHAPE', 'manifest must be an object');
|
|
386
384
|
if (typeof raw.schema === 'string' && Buffer.byteLength(raw.schema, 'utf8') > MAX_STRING)
|
|
387
385
|
return diagnostic('PLAN_LIMIT', 'schema exceeds maximum string size');
|
|
388
|
-
if (typeof raw.schema === 'string' && raw.schema.startsWith('compact-slices/') && raw.schema !== 'compact-slices/v1')
|
|
386
|
+
if (typeof raw.schema === 'string' && raw.schema.startsWith('compact-slices/') && raw.schema !== 'compact-slices/v1' && !(raw.schema === 'compact-slices/v2' && start === START_V2))
|
|
389
387
|
return unsupported(raw.schema);
|
|
390
388
|
if (!exact(raw, ['schema', 'planId', 'requirements', 'sources', 'commands', 'slices', 'closureCommands']))
|
|
391
389
|
return diagnostic(Object.keys(raw).some((key) => !['schema', 'planId', 'requirements', 'sources', 'commands', 'slices', 'closureCommands'].includes(key)) ? 'PLAN_UNKNOWN_FIELD' : 'PLAN_MISSING_FIELD', 'manifest fields must exactly match the contract');
|
|
@@ -395,7 +393,7 @@ function validatePlan(planPath, cwd, snapshot) {
|
|
|
395
393
|
return diagnostic('PLAN_LIMIT', 'manifest exceeds collection limits');
|
|
396
394
|
if (typeof raw.planId === 'string' && Buffer.byteLength(raw.planId, 'utf8') > MAX_STRING)
|
|
397
395
|
return diagnostic('PLAN_LIMIT', 'plan id exceeds maximum string size');
|
|
398
|
-
if (raw.schema !== 'compact-slices/v1' || typeof raw.planId !== 'string' || !PLAN_ID.test(raw.planId) || !allStrings(raw.requirements) || new Set(raw.requirements).size !== raw.requirements.length)
|
|
396
|
+
if ((raw.schema !== 'compact-slices/v1' && raw.schema !== 'compact-slices/v2') || (raw.schema === 'compact-slices/v1' && start !== START) || (raw.schema === 'compact-slices/v2' && start !== START_V2) || typeof raw.planId !== 'string' || !PLAN_ID.test(raw.planId) || !allStrings(raw.requirements) || new Set(raw.requirements).size !== raw.requirements.length)
|
|
399
397
|
return diagnostic('PLAN_SHAPE', 'manifest scalar fields are invalid');
|
|
400
398
|
if (raw.requirements.some((id) => !validRequirementId(id)) || !allStrings(raw.closureCommands) || new Set(raw.closureCommands).size !== raw.closureCommands.length)
|
|
401
399
|
return diagnostic('PLAN_SHAPE', 'manifest arrays or identifiers are invalid');
|
|
@@ -438,8 +436,9 @@ function validatePlan(planPath, cwd, snapshot) {
|
|
|
438
436
|
}
|
|
439
437
|
const sliceIds = new Set();
|
|
440
438
|
const owners = new Map();
|
|
439
|
+
const sliceFields = ['id', 'title', 'requirements', 'dependsOn', 'sectionAnchor', 'sources', 'redCommands', 'greenCommands', 'reviewEvidence', 'risk', 'fallback'];
|
|
441
440
|
for (const slice of raw.slices) {
|
|
442
|
-
if (!exact(slice,
|
|
441
|
+
if (!exact(slice, raw.schema === 'compact-slices/v2' ? [...sliceFields, 'implementerProfile'] : sliceFields) || (raw.schema === 'compact-slices/v2' && !['mechanical', 'integration', 'judgment'].includes(slice.implementerProfile)) || !validId(slice.id) || sliceIds.has(slice.id) || typeof slice.title !== 'string' || slice.title.length === 0 || Buffer.byteLength(slice.title, 'utf8') > MAX_STRING || !allStrings(slice.requirements) || slice.requirements.length === 0 || slice.requirements.some((id) => !validRequirementId(id) || !raw.requirements.includes(id)) || !allStrings(slice.dependsOn) || typeof slice.sectionAnchor !== 'string' || slice.sectionAnchor.length === 0 || Buffer.byteLength(slice.sectionAnchor, 'utf8') > MAX_STRING || !uniqueRefs(slice.sources, sourceIds) || !uniqueRefs(slice.redCommands, commandIds) || slice.redCommands.length === 0 || !uniqueRefs(slice.greenCommands, commandIds) || slice.greenCommands.length === 0 || !allStrings(slice.fallback) || slice.fallback.length === 0 || slice.fallback.some((item) => item.trim().length === 0) || slice.risk !== 'bounded' && slice.risk !== 'full-context')
|
|
443
442
|
return diagnostic('PLAN_SLICE_SHAPE', 'slice fields are invalid');
|
|
444
443
|
sliceIds.add(slice.id);
|
|
445
444
|
if (!Array.isArray(slice.reviewEvidence) || slice.reviewEvidence.length !== 2 || new Set(slice.reviewEvidence).size !== 2 || !slice.reviewEvidence.includes('specification') || !slice.reviewEvidence.includes('code-quality'))
|
|
@@ -485,7 +484,7 @@ function validatePlan(planPath, cwd, snapshot) {
|
|
|
485
484
|
if (markdown)
|
|
486
485
|
return markdown;
|
|
487
486
|
const report = {
|
|
488
|
-
state: 'valid', schema:
|
|
487
|
+
state: 'valid', schema: raw.schema,
|
|
489
488
|
planDigest: crypto_1.default.createHash('sha256').update(text, 'utf8').digest('hex'),
|
|
490
489
|
executionDigest: (0, identity_1.executionPlanDigest)(text),
|
|
491
490
|
manifest: raw, executionMode: executionModeFromValidatedText(text),
|
package/dist/src/index.js
CHANGED
|
@@ -46,6 +46,7 @@ const track_1 = require("./commands/track");
|
|
|
46
46
|
const evidence_1 = require("./commands/evidence");
|
|
47
47
|
const process_1 = require("./commands/process");
|
|
48
48
|
const context_1 = require("./commands/context");
|
|
49
|
+
const model_policy_1 = require("./commands/model-policy");
|
|
49
50
|
const add_1 = require("./commands/add");
|
|
50
51
|
const sync_1 = require("./commands/sync");
|
|
51
52
|
const update_1 = require("./commands/update");
|
|
@@ -54,6 +55,7 @@ const update_check_1 = require("./core/update-check");
|
|
|
54
55
|
const cli_version_1 = require("./core/cli-version");
|
|
55
56
|
exports.program = new commander_1.Command();
|
|
56
57
|
exports.program.name('awm').description('Agentic Workflow Manager').version((0, cli_version_1.cliVersion)());
|
|
58
|
+
(0, model_policy_1.registerModelPolicyCommand)(exports.program);
|
|
57
59
|
exports.program.hook('postAction', (_command, actionCommand) => {
|
|
58
60
|
if (actionCommand.name() === 'preflight')
|
|
59
61
|
return;
|
|
@@ -55,6 +55,22 @@ function passingState() {
|
|
|
55
55
|
return s;
|
|
56
56
|
}
|
|
57
57
|
describe('gate', () => {
|
|
58
|
+
test('un verdict routed exige intento observado, identidad y fingerprint coincidentes', () => {
|
|
59
|
+
const s = passingState();
|
|
60
|
+
s.verdicts[0] = { ...s.verdicts[0], routingAttemptId: 'route-1' };
|
|
61
|
+
let gate = (0, gate_1.computeGate)(s, false, fpCurrent);
|
|
62
|
+
expect(gate.reasons.some((reason) => reason.category === 'routing-evidence')).toBe(true);
|
|
63
|
+
s.routingAttempts = [{
|
|
64
|
+
schema: 'routing-attempt/v1', id: 'route-1', obligationId: 'o-spec', lineageId: 'lineage-1', attempt: 1,
|
|
65
|
+
envelope: { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'specification-reviewer', requestedProfile: 'full', effectiveProfile: 'full', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'high' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) },
|
|
66
|
+
envelopeDigest: 'a'.repeat(64), fingerprint: 'fp', state: 'active', nativeAgentId: 'native-1', observed: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'high' } },
|
|
67
|
+
}];
|
|
68
|
+
s.planBinding = { path: 'plan.md', digest: 'c'.repeat(64), executionDigest: 'd'.repeat(64), executionIdentitySchema: 'awm-plan-execution/v1', schema: 'compact-slices/v2', executionMode: 'desatendido', boundAt: 'now' };
|
|
69
|
+
expect((0, gate_1.computeGate)(s, false, fpCurrent).pass).toBe(true);
|
|
70
|
+
s.routingAttempts[0].fingerprint = 'different';
|
|
71
|
+
gate = (0, gate_1.computeGate)(s, false, fpCurrent);
|
|
72
|
+
expect(gate.reasons.some((reason) => reason.category === 'routing-evidence')).toBe(true);
|
|
73
|
+
});
|
|
58
74
|
test('el estado de referencia pasa; la corrupcion bloquea (R3.2)', () => {
|
|
59
75
|
expect((0, gate_1.computeGate)(passingState(), false, fpCurrent).pass).toBe(true);
|
|
60
76
|
const g = (0, gate_1.computeGate)(null, true, fpCurrent);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const gate_1 = require("../../../src/commands/job/gate");
|
|
4
|
+
const types_1 = require("../../../src/core/journal/types");
|
|
5
|
+
test('routed evidence cannot certify against a legacy journal without its attempt', () => {
|
|
6
|
+
const state = (0, types_1.emptyState)('main');
|
|
7
|
+
state.verdicts.push({ id: 'v1', obligationId: 'o1', result: 'pass', detail: '', receivedAt: 'now', fingerprint: 'fp', argv: [], paths: [], cwd: '.', routingAttemptId: 'missing' });
|
|
8
|
+
expect((0, gate_1.computeGate)(state, false, () => 'fp').reasons).toContainEqual(expect.objectContaining({ category: 'routing-evidence' }));
|
|
9
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const job_1 = require("../../../src/commands/job");
|
|
12
|
+
const store_1 = require("../../../src/core/journal/store");
|
|
13
|
+
const journal_1 = require("../../../src/core/model-policy/journal");
|
|
14
|
+
const envelope = { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'implementer', sliceId: 'S1', requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: { selector: { kind: 'model', id: 'model-private' }, effort: { kind: 'explicit', value: 'medium' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) };
|
|
15
|
+
function gitInit(repo) {
|
|
16
|
+
(0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', 'init', '-q', '-b', 'main'], { cwd: repo });
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'fixture'), 'x');
|
|
18
|
+
(0, child_process_1.execFileSync)('git', ['add', '.'], { cwd: repo });
|
|
19
|
+
(0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', 'commit', '-qm', 'fixture'], { cwd: repo });
|
|
20
|
+
}
|
|
21
|
+
test('`job routing-report --json` emits only aggregate routing evidence', async () => {
|
|
22
|
+
const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-routing-report-'));
|
|
23
|
+
try {
|
|
24
|
+
gitInit(repo);
|
|
25
|
+
(0, store_1.initJournal)(repo, 'main');
|
|
26
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
27
|
+
const reserved = (0, journal_1.reserveRoutingAttempt)(state, { obligationId: 'obligation', lineageId: 'lineage', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z');
|
|
28
|
+
(0, store_1.writeJournal)(repo, 'main', (0, journal_1.observeRoutingAttempt)(reserved.state, { attemptId: reserved.attemptId, nativeAgentId: 'native-private' }, '2026-09-17T00:01:00.000Z'));
|
|
29
|
+
const program = new commander_1.Command();
|
|
30
|
+
program.exitOverride();
|
|
31
|
+
(0, job_1.registerJobCommand)(program);
|
|
32
|
+
const out = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
33
|
+
const cwd = jest.spyOn(process, 'cwd').mockReturnValue(repo);
|
|
34
|
+
await program.parseAsync(['node', 'awm', 'job', 'routing-report', '--json']);
|
|
35
|
+
const rendered = String(out.mock.calls[0][0]);
|
|
36
|
+
const report = JSON.parse(rendered);
|
|
37
|
+
expect(report).toMatchObject({ schema: 'routing-report/v1', attempts: 1, plannedByRole: { implementer: 1 }, actualByRole: { implementer: 1 }, byState: { active: 1 } });
|
|
38
|
+
expect(rendered).not.toContain('native-private');
|
|
39
|
+
expect(rendered).not.toContain('model-private');
|
|
40
|
+
cwd.mockRestore();
|
|
41
|
+
out.mockRestore();
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
fs_1.default.rmSync(repo, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const types_1 = require("../../../src/core/journal/types");
|
|
4
|
+
const journal_1 = require("../../../src/core/model-policy/journal");
|
|
5
|
+
const envelope = { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'implementer', sliceId: 'S1', requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'medium' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) };
|
|
6
|
+
test('routing report remains read-only across an observed attempt', () => {
|
|
7
|
+
const reserved = (0, journal_1.reserveRoutingAttempt)((0, types_1.emptyState)('main'), { obligationId: 'o1', lineageId: 'l1', envelope, fingerprint: 'e'.repeat(64) }, '2026-09-17T00:00:00.000Z');
|
|
8
|
+
const observed = (0, journal_1.observeRoutingAttempt)(reserved.state, { attemptId: reserved.attemptId, nativeAgentId: 'native-id' }, '2026-09-17T00:01:00.000Z');
|
|
9
|
+
expect((0, journal_1.routingReport)(observed)).toMatchObject({ attempts: 1, byState: { active: 1 } });
|
|
10
|
+
expect(observed.routingAttempts[0].nativeAgentId).toBe('native-id');
|
|
11
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commander_1 = require("commander");
|
|
4
|
+
const model_policy_1 = require("../../../src/commands/model-policy");
|
|
5
|
+
const out = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
6
|
+
describe('model-policy command', () => {
|
|
7
|
+
beforeEach(() => { jest.clearAllMocks(); process.exitCode = undefined; });
|
|
8
|
+
afterAll(() => out.mockRestore());
|
|
9
|
+
it('emits a read-only protocol contract as JSON', async () => {
|
|
10
|
+
const program = new commander_1.Command();
|
|
11
|
+
program.exitOverride();
|
|
12
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
13
|
+
(0, model_policy_1.registerModelPolicyCommand)(program);
|
|
14
|
+
await program.parseAsync(['node', 'awm', 'model-policy', 'contract', '--json']);
|
|
15
|
+
expect(JSON.parse(String(out.mock.calls[0][0]))).toMatchObject({
|
|
16
|
+
schema: 'routing-protocol/v1',
|
|
17
|
+
supportedPlanSchemas: ['compact-slices/v1', 'compact-slices/v2'],
|
|
18
|
+
implementerProfiles: ['mechanical', 'integration', 'judgment'],
|
|
19
|
+
limits: { maxFileBytes: 262144 },
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it('rejects missing values instead of silently accepting an incomplete approval', async () => {
|
|
23
|
+
const program = new commander_1.Command();
|
|
24
|
+
program.exitOverride();
|
|
25
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
26
|
+
(0, model_policy_1.registerModelPolicyCommand)(program);
|
|
27
|
+
await expect(program.parseAsync(['node', 'awm', 'model-policy', 'approve', '--file', '--scope', 'user', '--expected-digest', 'a'.repeat(64)])).rejects.toThrow();
|
|
28
|
+
});
|
|
29
|
+
it('accepts and validates --cwd on contract, and resolves status through it', async () => {
|
|
30
|
+
const readEffectivePolicy = jest.fn(() => ({ state: 'absent' }));
|
|
31
|
+
const program = new commander_1.Command();
|
|
32
|
+
program.exitOverride();
|
|
33
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
34
|
+
(0, model_policy_1.registerModelPolicyCommand)(program, { readEffectivePolicy });
|
|
35
|
+
await program.parseAsync(['node', 'awm', 'model-policy', 'contract', '--cwd', 'fixture-root', '--json']);
|
|
36
|
+
await program.parseAsync(['node', 'awm', 'model-policy', 'status', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--cwd', 'fixture-root', '--json']);
|
|
37
|
+
expect(readEffectivePolicy).toHaveBeenCalledWith('fixture-root');
|
|
38
|
+
await expect(program.parseAsync(['node', 'awm', 'model-policy', 'contract', '--cwd', '--json'])).rejects.toThrow();
|
|
39
|
+
});
|
|
40
|
+
it('reports receipt provenance in status and rejects stale receipt state', async () => {
|
|
41
|
+
const readCapabilities = jest.fn(() => ({ state: 'stale', reason: 'expired' }));
|
|
42
|
+
const program = new commander_1.Command();
|
|
43
|
+
program.exitOverride();
|
|
44
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
45
|
+
(0, model_policy_1.registerModelPolicyCommand)(program, { readEffectivePolicy: () => ({ state: 'absent' }), readCapabilities });
|
|
46
|
+
await program.parseAsync(['node', 'awm', 'model-policy', 'status', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--json']);
|
|
47
|
+
expect(JSON.parse(String(out.mock.calls[0][0]))).toMatchObject({ policy: { state: 'absent' }, capability: { state: 'stale' } });
|
|
48
|
+
expect(process.exitCode).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
it('rejects an empty replacement digest for capability approval at the command boundary', async () => {
|
|
51
|
+
const program = new commander_1.Command();
|
|
52
|
+
program.exitOverride();
|
|
53
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
54
|
+
(0, model_policy_1.registerModelPolicyCommand)(program, { approveCapabilities: jest.fn() });
|
|
55
|
+
await expect(program.parseAsync(['node', 'awm', 'model-policy', 'capabilities', 'approve', '--file', 'receipt.json', '--expected-digest', 'a'.repeat(64), '--replace-digest', ''])).rejects.toThrow(/requires a non-empty value/);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -48,6 +48,18 @@ describe('plan validate Commander wiring', () => {
|
|
|
48
48
|
stdoutWrite.mockRestore();
|
|
49
49
|
process.exitCode = previousExitCode;
|
|
50
50
|
});
|
|
51
|
+
it('returns v1 not-required before reading personal policy or capability state', async () => {
|
|
52
|
+
const policy = jest.fn();
|
|
53
|
+
const capabilities = jest.fn();
|
|
54
|
+
const program = new commander_1.Command();
|
|
55
|
+
program.exitOverride();
|
|
56
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
57
|
+
(0, plan_1.registerPlanCommand)(program, { validatePlanFile: () => valid, readEffectivePolicy: policy, readCapabilities: capabilities });
|
|
58
|
+
await program.parseAsync(['node', 'awm', 'plan', 'resolve', 'plans/r4.md', '--provider', 'codex', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--role', 'controller', '--json']);
|
|
59
|
+
expect(JSON.parse(String(stdoutWrite.mock.calls[0][0]))).toEqual({ state: 'not-required', reason: 'v1-without-opt-in' });
|
|
60
|
+
expect(policy).not.toHaveBeenCalled();
|
|
61
|
+
expect(capabilities).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
51
63
|
it('emits deterministic human output for a valid compact plan', async () => {
|
|
52
64
|
await commandFor(valid).parseAsync(['node', 'awm', 'plan', 'validate', 'plans/r4.md']);
|
|
53
65
|
expect(String(stdoutWrite.mock.calls[0][0])).toBe('Plan validation: valid "plans/r4.md" (compact-slices/v1; 1 slices; 5 requirements; complete ownership)\n');
|
|
@@ -192,6 +204,46 @@ describe('plan validate Commander wiring', () => {
|
|
|
192
204
|
});
|
|
193
205
|
});
|
|
194
206
|
describe('plan admit Commander wiring', () => {
|
|
207
|
+
it('does not read v2 routing facts after currentness blocks', async () => {
|
|
208
|
+
const policy = jest.fn();
|
|
209
|
+
const capabilities = jest.fn();
|
|
210
|
+
const output = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
211
|
+
const program = new commander_1.Command();
|
|
212
|
+
program.exitOverride();
|
|
213
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
214
|
+
(0, plan_1.registerPlanCommand)(program, { validatePlanFile: () => ({ ...valid, schema: 'compact-slices/v2', manifest: { ...valid.manifest, schema: 'compact-slices/v2', slices: valid.manifest.slices.map(slice => ({ ...slice, implementerProfile: 'mechanical' })) } }), readPreferences: () => ({ defaultAgent: 'codex', enabledAgents: ['codex'], installMethod: 'symlink', defaultScope: 'local' }), readEffectivePolicy: policy, readCapabilities: capabilities, listRegistries: () => [], checkCurrentness: async () => ({ checkedAt: 'x', compatibility: { status: 'not-checked' }, components: [{ component: 'cli', installed: '1', latest: '2', channel: 'stable', source: 'x', checkedAt: 'x', status: 'stale', detail: 'x', remedy: 'x' }] }) });
|
|
215
|
+
try {
|
|
216
|
+
await program.parseAsync(['node', 'awm', 'plan', 'admit', 'plan.md', '--provider', 'codex', '--cwd', repositoryRoot, '--require-current', '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--json']);
|
|
217
|
+
expect(policy).not.toHaveBeenCalled();
|
|
218
|
+
expect(capabilities).not.toHaveBeenCalled();
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
output.mockRestore();
|
|
222
|
+
process.exitCode = undefined;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
it.each([
|
|
226
|
+
['invalid plan', invalid, 'codex', ['codex']],
|
|
227
|
+
['invalid provider', valid, 'not-a-provider', ['codex']],
|
|
228
|
+
['disabled provider', valid, 'codex', ['cursor']],
|
|
229
|
+
])('does not read routing facts for %s even with complete runtime flags', async (_name, report, provider, enabledAgents) => {
|
|
230
|
+
const policy = jest.fn();
|
|
231
|
+
const capabilities = jest.fn();
|
|
232
|
+
const output = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
233
|
+
const program = new commander_1.Command();
|
|
234
|
+
program.exitOverride();
|
|
235
|
+
program.configureOutput({ writeErr: () => undefined });
|
|
236
|
+
(0, plan_1.registerPlanCommand)(program, { validatePlanFile: () => report, readPreferences: () => ({ defaultAgent: 'codex', enabledAgents: [...enabledAgents], installMethod: 'symlink', defaultScope: 'local' }), readEffectivePolicy: policy, readCapabilities: capabilities });
|
|
237
|
+
try {
|
|
238
|
+
await program.parseAsync(['node', 'awm', 'plan', 'admit', 'plan.md', '--provider', provider, '--cwd', repositoryRoot, '--runtime-kind', 'native', '--runtime-version', '1.0.0', '--account-scope-digest', 'a'.repeat(64), '--json']);
|
|
239
|
+
expect(policy).not.toHaveBeenCalled();
|
|
240
|
+
expect(capabilities).not.toHaveBeenCalled();
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
output.mockRestore();
|
|
244
|
+
process.exitCode = undefined;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
195
247
|
it('derives desatendido from the canonical plan header and requires a journal without a flag', async () => {
|
|
196
248
|
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-admit-header-'));
|
|
197
249
|
const output = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
@@ -194,6 +194,18 @@ describe('aplicacion transaccional de requests', () => {
|
|
|
194
194
|
expect(Object.values(s.appliedRequests).some((a) => a.outcome === 'rejected-stale-generation')).toBe(true);
|
|
195
195
|
expect(s.controllerHeartbeatAt).toBeUndefined();
|
|
196
196
|
});
|
|
197
|
+
test('routing reserve/observe es supervisado, idempotente y una generacion vieja no muta intentos', () => {
|
|
198
|
+
const envelope = { schema: 'routing-envelope/v1', runtime: { target: 'codex', kind: 'native', version: '1', accountScopeDigest: '0'.repeat(64) }, role: 'implementer', sliceId: 'S1', requestedProfile: 'mechanical', effectiveProfile: 'mechanical', resolved: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'medium' } }, outcome: 'native', unavailableEvidence: [], policyDigest: 'a'.repeat(64), capabilityDigest: 'b'.repeat(64), planDigest: 'c'.repeat(64), executionDigest: 'd'.repeat(64) };
|
|
199
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-reserve', generationToken: 'g1', idempotencyKey: 'routing-1', payload: { obligationId: 'o1', lineageId: 'l1', envelope, fingerprint: 'e'.repeat(64) } });
|
|
200
|
+
(0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
|
|
201
|
+
const attempt = (0, store_1.readJournal)(repo, 'rama').state.routingAttempts[0];
|
|
202
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-observe', generationToken: 'g1', idempotencyKey: 'routing-2', payload: { attemptId: attempt.id, nativeAgentId: 'native-1' } });
|
|
203
|
+
(0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
|
|
204
|
+
expect((0, store_1.readJournal)(repo, 'rama').state.routingAttempts[0]).toMatchObject({ state: 'active', nativeAgentId: 'native-1' });
|
|
205
|
+
(0, requests_1.emitRequest)(repo, 'rama', { kind: 'routing-reserve', generationToken: 'old', idempotencyKey: 'routing-old', payload: { obligationId: 'o1', lineageId: 'l2', envelope, fingerprint: 'f'.repeat(64) } });
|
|
206
|
+
expect((0, apply_1.consumePendingRequests)(repo, 'rama', 'g1').rejectedStale).toBe(1);
|
|
207
|
+
expect((0, store_1.readJournal)(repo, 'rama').state.routingAttempts).toHaveLength(1);
|
|
208
|
+
});
|
|
197
209
|
test('request corrupta se aparta VISIBLE como .corrupt, jamas se descarta (R1.6)', () => {
|
|
198
210
|
fs_1.default.writeFileSync(path_1.default.join((0, paths_1.requestsDir)(repo, 'rama'), 'req-roto.json'), '{no-json');
|
|
199
211
|
const out = (0, apply_1.consumePendingRequests)(repo, 'rama', 'g1');
|
|
@@ -11,6 +11,54 @@ const valid = {
|
|
|
11
11
|
},
|
|
12
12
|
};
|
|
13
13
|
describe('admitPlan', () => {
|
|
14
|
+
const sha = 'a'.repeat(64);
|
|
15
|
+
const routing = () => ({
|
|
16
|
+
now: new Date('2026-09-17T12:00:00.000Z'),
|
|
17
|
+
runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha },
|
|
18
|
+
policy: { schema: 'approved-model-policy/v1', content: { schema: 'model-policy/v1', mappings: [{ target: 'codex', runtimeKind: 'native', profiles: { mechanical: { selector: { kind: 'model', id: 'm' }, effort: { kind: 'explicit', value: 'low' } }, integration: { selector: { kind: 'model', id: 'i' }, effort: { kind: 'explicit', value: 'medium' } }, judgment: { selector: { kind: 'model', id: 'j' }, effort: { kind: 'explicit', value: 'medium' } } }, fullCapability: { selector: { kind: 'model', id: 'f' }, effort: { kind: 'explicit', value: 'high' } }, degradation: { allowMissingModelOverride: false, allowMissingEffortOverride: false, allowMissingObservedIdentity: false } }], implementationBudget: { maxAttempts: 3, escalation: ['mechanical', 'integration', 'judgment'], judgmentEfforts: ['medium', 'high'] } }, contentDigest: sha, approval: { approvedAt: '2026-09-17T00:00:00.000Z', approvalId: 'approval' }, lineage: { previousDigest: null } },
|
|
19
|
+
capabilities: { schema: 'routing-capabilities/v1', runtime: { target: 'codex', kind: 'native', version: '1.0.0', accountScopeDigest: sha }, recordedAt: '2026-09-17T00:00:00.000Z', expiresAt: '2026-09-18T00:00:00.000Z', capabilities: { artifactDelivery: 'unverified', interactiveExecution: 'supported', unattendedController: 'supported', nativeSubagents: 'unverified', modelOverride: 'supported', effortOverride: 'supported', observedModelEvidence: 'supported', durableResume: 'unverified' }, availableSelections: ['m', 'i', 'j', 'f'].map((id, n) => ({ selector: { kind: 'model', id }, effort: { kind: 'explicit', value: n === 0 ? 'low' : n === 3 ? 'high' : 'medium' } })), evidence: ['interactiveExecution', 'unattendedController', 'modelOverride', 'effortOverride', 'observedModelEvidence'].map(capability => ({ capability: capability, kind: 'native-control', receiptDigest: sha })), approval: { approvalId: 'approval', snapshotDigest: sha } },
|
|
20
|
+
});
|
|
21
|
+
const v2 = (profile = 'mechanical') => ({ ...valid, schema: 'compact-slices/v2', manifest: { ...valid.manifest, schema: 'compact-slices/v2', slices: valid.manifest.slices.map(slice => ({ ...slice, implementerProfile: profile })) } });
|
|
22
|
+
it('blocks v2 routing only after existing sensors pass when routing facts are absent', async () => {
|
|
23
|
+
const report = await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], verifySensors: true, sensors: { overall: 'pass', sensors: [] } });
|
|
24
|
+
expect(report).toMatchObject({ state: 'blocked', sensors: 'pass' });
|
|
25
|
+
expect(report.diagnostics[0].code).toBe('ADMISSION_ROUTING_FACTS_REQUIRED');
|
|
26
|
+
});
|
|
27
|
+
it.each([
|
|
28
|
+
['journal', { provider: 'codex', executionMode: 'desatendido' }],
|
|
29
|
+
['provider capability', { provider: 'cursor', executionMode: 'interactivo' }],
|
|
30
|
+
])('does not invoke deferred v2 routing facts when %s gate blocks', async (_name, fields) => {
|
|
31
|
+
const reader = jest.fn(() => { throw new Error('routing reader must not run'); });
|
|
32
|
+
const report = await (0, admission_1.admitPlan)({ plan: v2(), cwd: process.cwd(), enabledAgents: [fields.provider], routingReader: reader, ...fields });
|
|
33
|
+
expect(report.state).toBe('blocked');
|
|
34
|
+
expect(reader).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
it.each(['fail', 'not_certified'])('does not invoke deferred v2 routing facts when sensors are %s', async (overall) => {
|
|
37
|
+
const reader = jest.fn(() => { throw new Error('routing reader must not run'); });
|
|
38
|
+
const report = await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], verifySensors: true, sensors: { overall, sensors: [] }, routingReader: reader });
|
|
39
|
+
expect(report).toMatchObject({ state: 'blocked', sensors: overall === 'not_certified' ? 'not-certified' : 'fail' });
|
|
40
|
+
expect(reader).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
it('does not invoke deferred v2 routing facts when compatibility blocks', async () => {
|
|
43
|
+
const reader = jest.fn(() => { throw new Error('routing reader must not run'); });
|
|
44
|
+
const currentness = { checkedAt: 'x', compatibility: { status: 'not-checked' }, components: [{ component: 'cli', installed: '1', latest: '1', channel: 'stable', source: 'x', checkedAt: 'x', status: 'current', detail: 'x', remedy: 'none' }] };
|
|
45
|
+
const report = await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], requireCurrent: true, provenance: 'proven', currentness, compatibilityDiagnostics: [{ code: 'COMPAT', message: 'blocked' }], routingReader: reader });
|
|
46
|
+
expect(report).toMatchObject({ state: 'blocked', diagnostics: [expect.objectContaining({ code: 'COMPAT' })] });
|
|
47
|
+
expect(reader).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
it.each(['mechanical', 'integration', 'judgment'])('admits v2 %s with policy and capability evidence and an honest unknown forecast', async (profile) => {
|
|
50
|
+
const report = await (0, admission_1.admitPlan)({ plan: v2(profile), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routing: routing() });
|
|
51
|
+
expect(report).toMatchObject({ state: 'admitted', routingForecast: { implementerProfiles: { [profile]: 1 }, trackB: { state: 'unavailable', lowerBound: 1 }, controller: { state: 'unavailable', lowerBound: 0 } } });
|
|
52
|
+
});
|
|
53
|
+
it('blocks a valid cross-provider policy and receipt instead of admitting codex under another runtime target', async () => {
|
|
54
|
+
const evidence = routing();
|
|
55
|
+
const foreign = structuredClone(evidence);
|
|
56
|
+
foreign.runtime.target = 'cursor';
|
|
57
|
+
foreign.policy.content.mappings[0].target = 'cursor';
|
|
58
|
+
foreign.capabilities.runtime.target = 'cursor';
|
|
59
|
+
const report = await (0, admission_1.admitPlan)({ plan: v2(), provider: 'codex', cwd: process.cwd(), enabledAgents: ['codex'], routing: foreign });
|
|
60
|
+
expect(report).toMatchObject({ state: 'blocked', provider: 'codex', diagnostics: [expect.objectContaining({ code: 'ADMISSION_ROUTING_PROVIDER_MISMATCH' })] });
|
|
61
|
+
});
|
|
14
62
|
it('rejects a forecast whose total or role topology does not match its slices', () => {
|
|
15
63
|
const forecast = { kind: 'topology', slices: 2, roles: { implementer: 2, 'specification-reviewer': 2, 'code-quality-reviewer': 2, 'final-reviewer': 1, 'track-a-qa': 1, 'track-b-qa': 1, documentation: 1, retro: 1, finishing: 1 }, total: 99 };
|
|
16
64
|
expect(() => (0, admission_1.sanitizeAdmissionReport)({ state: 'admitted', planState: 'valid', journal: 'not-required', currentness: 'not-checked', sensors: 'not-required', diagnostics: [], forecast })).toThrow(/invalid report/);
|
|
@@ -18,6 +66,11 @@ describe('admitPlan', () => {
|
|
|
18
66
|
forecast.roles.implementer = 1;
|
|
19
67
|
expect(() => (0, admission_1.sanitizeAdmissionReport)({ state: 'admitted', planState: 'valid', journal: 'not-required', currentness: 'not-checked', sensors: 'not-required', diagnostics: [], forecast })).toThrow(/invalid report/);
|
|
20
68
|
});
|
|
69
|
+
it('rejects forged routing forecast role keys and profile totals at the public sanitizer', () => {
|
|
70
|
+
const base = { kind: 'routing-v1', slices: 1, implementerProfiles: { mechanical: 1, integration: 0, judgment: 0 }, roles: { 'specification-reviewer': 1, 'code-quality-reviewer': 1, 'final-reviewer': 1, 'track-a-qa': 1, documentation: 1, retro: 1, finishing: 1 }, trackB: { state: 'unavailable', lowerBound: 1 }, controller: { state: 'unavailable', lowerBound: 0 } };
|
|
71
|
+
expect(() => (0, admission_1.sanitizeAdmissionReport)({ state: 'admitted', planState: 'valid', journal: 'not-required', currentness: 'not-checked', sensors: 'not-required', diagnostics: [], routingForecast: { ...base, roles: { ...base.roles, architecture: 1 } } })).toThrow(/invalid report/);
|
|
72
|
+
expect(() => (0, admission_1.sanitizeAdmissionReport)({ state: 'admitted', planState: 'valid', journal: 'not-required', currentness: 'not-checked', sensors: 'not-required', diagnostics: [], routingForecast: { ...base, implementerProfiles: { ...base.implementerProfiles, mechanical: 0 } } })).toThrow(/invalid report/);
|
|
73
|
+
});
|
|
21
74
|
it('preserves the distinct unsupported plan state and bounded validator diagnostic', async () => {
|
|
22
75
|
const report = await (0, admission_1.admitPlan)({ plan: { state: 'unsupported', schema: 'compact-slices/v9', diagnostics: [{ code: 'PLAN_UNSUPPORTED_SCHEMA', message: `future\u001b${'x'.repeat(5000)}` }] }, provider: 'codex', cwd: process.cwd() });
|
|
23
76
|
expect(report).toMatchObject({ state: 'blocked', planState: 'unsupported', currentness: 'not-checked', sensors: 'not-required' });
|