@aiwg/cli 2026.10.4 → 2026.10.6
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/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +3 -1
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +72 -9
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +146 -16
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +91 -26
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +130 -56
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/index-worker.mjs +9 -7
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/cli/handlers/decision.js +19 -1
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +40 -6
- package/dist/src/cli/handlers/setup.js +428 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +279 -93
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +8 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/benchmark.js +469 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +18 -15
- package/dist/src/decision/qualification/load-staged.js +539 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +233 -0
- package/dist/src/discovery/plan-apply.js +571 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/mcp/cli.mjs +5 -6
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +7 -0
- package/dist/src/models/worker-installation.mjs +169 -37
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +114 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +33 -19
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/batch-contracts.js +1 -1
- package/dist/src/sessions/batch-import.js +202 -2
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +73 -54
- package/dist/src/sessions/coverage-preflight.js +26 -1
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/policy.js +18 -7
- package/dist/src/sessions/refresh.js +2 -3
- package/dist/src/sessions/repository.js +4 -2
- package/dist/src/sessions/workspace-discovery.js +92 -26
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +19 -13
- package/tools/agents/providers/claude.mjs +1 -1
- package/tools/agents/providers/codex.mjs +50 -56
- package/tools/agents/providers/copilot.mjs +8 -2
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +7 -13
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +28 -4
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +263 -71
- package/tools/agents/providers/windsurf.mjs +7 -0
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +7 -4
- package/tools/ralph-external/session-launcher.mjs +280 -32
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { integer, dateTime, number } from '../schema/zod-validation.mjs';
|
|
3
|
+
import { costMemory, CostConfidenceSchema } from './cost.js';
|
|
4
|
+
import { sha256 } from './context-pack.js';
|
|
5
|
+
import { MemoryDigestSchema, MemoryRecordSchema } from './maintenance-record.js';
|
|
6
|
+
const Text = z.string().min(1);
|
|
7
|
+
const DateTime = dateTime();
|
|
8
|
+
/** Local starting points for workload evaluation, not research-established thresholds. */
|
|
9
|
+
export const RecommendationThresholdsSchema = z.strictObject({
|
|
10
|
+
minimumAgeDays: integer().nonnegative().default(30),
|
|
11
|
+
reviewAgeDays: integer().nonnegative().default(90),
|
|
12
|
+
demoteMinimumTokens: integer().nonnegative().default(512),
|
|
13
|
+
taskPackCharacters: integer().min(256).max(65536).default(8000),
|
|
14
|
+
});
|
|
15
|
+
const ItemEvidenceSchema = z.strictObject({
|
|
16
|
+
source: Text,
|
|
17
|
+
criticality: z.enum(['ordinary', 'critical']).optional(),
|
|
18
|
+
ownerPinned: z.boolean().optional(),
|
|
19
|
+
rareObligation: z.boolean().optional(),
|
|
20
|
+
provenance: z.enum(['verified', 'uncertain', 'conflicting']).optional(),
|
|
21
|
+
createdAt: DateTime.optional(), reviewedAt: DateTime.optional(),
|
|
22
|
+
history: z.enum(['known', 'unknown']).optional(),
|
|
23
|
+
obsolete: z.boolean().optional(),
|
|
24
|
+
supersededBy: Text.optional(), conflictsWith: z.array(Text).optional(),
|
|
25
|
+
categoryCorrection: Text.optional(), summaryStale: z.boolean().optional(),
|
|
26
|
+
residentProposal: z.strictObject({
|
|
27
|
+
owner: Text.optional(), rationale: Text.optional(), budgetTokens: integer().positive().optional(),
|
|
28
|
+
reviewDate: DateTime.optional(), boundedPackEvaluated: z.boolean().optional(),
|
|
29
|
+
}).optional(),
|
|
30
|
+
});
|
|
31
|
+
export const RecommendationInputsSchema = z.strictObject({
|
|
32
|
+
thresholds: RecommendationThresholdsSchema.prefault({}),
|
|
33
|
+
items: z.record(z.string(), ItemEvidenceSchema).prefault({}),
|
|
34
|
+
});
|
|
35
|
+
const CandidateSchema = z.strictObject({
|
|
36
|
+
id: Text,
|
|
37
|
+
kind: z.enum(['merge', 'recategorize', 'conflict', 'supersede', 'demote', 'promote', 'retire']),
|
|
38
|
+
target: MemoryRecordSchema,
|
|
39
|
+
sourceDigest: MemoryDigestSchema.nullable(),
|
|
40
|
+
related: z.array(Text),
|
|
41
|
+
relatedTargets: z.array(z.strictObject({ record: MemoryRecordSchema, sourceDigest: MemoryDigestSchema.nullable() })),
|
|
42
|
+
status: z.enum(['advisory', 'blocked', 'rejected', 'conflict']),
|
|
43
|
+
advisoryRank: integer().positive(),
|
|
44
|
+
rationale: Text,
|
|
45
|
+
evidence: z.array(z.strictObject({ source: Text, detail: Text })).min(1),
|
|
46
|
+
uncertainty: z.array(Text).min(1),
|
|
47
|
+
blockedReasons: z.array(Text), protectedItemExceptions: z.array(Text),
|
|
48
|
+
expectedSavings: z.strictObject({
|
|
49
|
+
residentTokens: number().nullable(), estimator: Text, confidence: CostConfidenceSchema, basis: Text,
|
|
50
|
+
}),
|
|
51
|
+
retrievalImpact: z.strictObject({
|
|
52
|
+
detail: Text, requiresVerification: z.literal(true),
|
|
53
|
+
proposedPlacement: z.enum(['indexed', 'task-context', 'resident']).nullable(),
|
|
54
|
+
category: Text.nullable(),
|
|
55
|
+
taskPack: z.strictObject({ builder: z.literal('buildWorkspaceContextPack'), totalCharacters: integer().positive() }),
|
|
56
|
+
}),
|
|
57
|
+
residentJustification: z.strictObject({
|
|
58
|
+
owner: Text.optional(), rationale: Text.optional(), budgetTokens: integer().positive().optional(),
|
|
59
|
+
reviewDate: DateTime.optional(), boundedPackEvaluated: z.boolean().optional(),
|
|
60
|
+
}).nullable(),
|
|
61
|
+
});
|
|
62
|
+
export const MemoryRecommendationsSchema = z.strictObject({
|
|
63
|
+
schemaVersion: z.literal('aiwg.memory-recommendations.v1'),
|
|
64
|
+
at: DateTime, workspace: Text, scope: Text,
|
|
65
|
+
advisoryOnly: z.literal(true), defaultPlacement: z.literal('indexed'),
|
|
66
|
+
thresholds: RecommendationThresholdsSchema,
|
|
67
|
+
coverage: z.strictObject({ complete: z.boolean(), limitsHit: z.array(Text), exclusions: z.array(Text) }),
|
|
68
|
+
candidates: z.array(CandidateSchema),
|
|
69
|
+
notes: z.array(Text),
|
|
70
|
+
});
|
|
71
|
+
/** Pure policy over audit + cost evidence. No file, provider or telemetry writes. */
|
|
72
|
+
export function recommendMemory(audit, input = {}, costOptions = {}) {
|
|
73
|
+
const config = RecommendationInputsSchema.parse(input);
|
|
74
|
+
const at = costOptions.at ?? audit.at;
|
|
75
|
+
const now = Date.parse(DateTime.parse(at));
|
|
76
|
+
const cost = costMemory(audit, { ...costOptions, at,
|
|
77
|
+
demote: audit.items.filter(i => i.record?.placement === 'resident').map(i => i.id) });
|
|
78
|
+
const costs = new Map(cost.items.map(i => [i.id, i]));
|
|
79
|
+
const items = new Map(audit.items.map(i => [i.id, i]));
|
|
80
|
+
for (const id of Object.keys(config.items))
|
|
81
|
+
if (!items.has(id))
|
|
82
|
+
throw new Error(`Recommendation evidence target is outside the audit inventory: ${id}`);
|
|
83
|
+
const candidates = [];
|
|
84
|
+
const age = (date) => date ? (now - Date.parse(date)) / 86400000 : null;
|
|
85
|
+
const metadata = (id) => config.items[id];
|
|
86
|
+
const protections = (id) => {
|
|
87
|
+
const record = items.get(id)?.record;
|
|
88
|
+
const info = metadata(id);
|
|
89
|
+
return [
|
|
90
|
+
...(record?.authority.protection === 'pinned' || info?.ownerPinned ? ['Owner pin protects this item'] : []),
|
|
91
|
+
...(info?.rareObligation ? ['Rare obligation must remain available'] : []),
|
|
92
|
+
...(info?.criticality === 'critical' ? ['Critical rule requires explicit owner disposition'] : []),
|
|
93
|
+
];
|
|
94
|
+
};
|
|
95
|
+
const conflicts = new Set();
|
|
96
|
+
for (const finding of audit.findings)
|
|
97
|
+
if (/conflict|collision/.test(finding.code)) {
|
|
98
|
+
for (const item of audit.items)
|
|
99
|
+
if (finding.paths.includes(item.sourcePath) || item.exposures.some(e => finding.paths.includes(e.path)))
|
|
100
|
+
conflicts.add(item.id);
|
|
101
|
+
}
|
|
102
|
+
for (const [id, info] of Object.entries(config.items)) {
|
|
103
|
+
if (info.provenance === 'conflicting' || info.conflictsWith?.length) {
|
|
104
|
+
conflicts.add(id);
|
|
105
|
+
for (const related of info.conflictsWith ?? [])
|
|
106
|
+
if (items.has(related))
|
|
107
|
+
conflicts.add(related);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function add(id, kind, rationale, related = [], reasons = [], placement = null) {
|
|
111
|
+
const item = items.get(id);
|
|
112
|
+
if (!item.record)
|
|
113
|
+
return;
|
|
114
|
+
const info = metadata(id);
|
|
115
|
+
const attribution = costs.get(id);
|
|
116
|
+
const exceptions = [...new Set([id, ...related].flatMap(protections))];
|
|
117
|
+
const blocked = [...reasons];
|
|
118
|
+
const destructive = ['retire', 'demote', 'merge', 'supersede'].includes(kind);
|
|
119
|
+
if (destructive)
|
|
120
|
+
blocked.push(...exceptions);
|
|
121
|
+
if (kind !== 'conflict' && conflicts.has(id))
|
|
122
|
+
blocked.push('Conflicting source evidence needs owner resolution before changes');
|
|
123
|
+
if (destructive && item.record.lifecycle !== 'active')
|
|
124
|
+
blocked.push('Only active originals are eligible');
|
|
125
|
+
let tokens = null;
|
|
126
|
+
let confidence = 'unknown';
|
|
127
|
+
let basis = 'No supported saving estimate; retained history and live activation are unmeasured';
|
|
128
|
+
if (kind === 'demote') {
|
|
129
|
+
const preview = cost.demotionPreview.targets.find(t => t.id === id);
|
|
130
|
+
tokens = attribution.tokens === null ? null : attribution.tokens - preview.after.anchorTokens;
|
|
131
|
+
confidence = tokens === null ? 'unknown' : 'inferred';
|
|
132
|
+
basis = 'Conditional static resident bytes minus cost.ts retrieval anchors; provider regeneration and retrieval proof still required';
|
|
133
|
+
}
|
|
134
|
+
candidates.push({
|
|
135
|
+
id: sha256(JSON.stringify({ kind, id, related: [...related].sort(), placement, category: info?.categoryCorrection ?? null })),
|
|
136
|
+
kind, target: item.record, sourceDigest: item.sourceDigest ?? item.digest, related,
|
|
137
|
+
relatedTargets: related.flatMap(key => {
|
|
138
|
+
const input = items.get(key);
|
|
139
|
+
return input?.record ? [{ record: input.record, sourceDigest: input.sourceDigest ?? input.digest }] : [];
|
|
140
|
+
}),
|
|
141
|
+
status: kind === 'conflict' ? 'conflict' : blocked.length ? 'blocked' : 'advisory', advisoryRank: 1,
|
|
142
|
+
rationale, evidence: [
|
|
143
|
+
{ source: item.observation.evidence.source, detail: `Audit: ${item.observation.status}; ${item.category}; ${item.record.placement}; review ${item.reviewStatus}` },
|
|
144
|
+
{ source: attribution.estimator, detail: `Cost ${attribution.tokens ?? 'unknown'} tokens (${attribution.confidence}); utility ${attribution.utility.outcome}; evidence ${attribution.utility.evidence ?? 'unknown'}` },
|
|
145
|
+
...(info ? [{ source: info.source, detail: `Owner evidence: provenance ${info.provenance ?? 'unknown'}; history ${info.history ?? 'unknown'}; created ${info.createdAt ?? 'unknown'}; reviewed ${info.reviewedAt ?? 'unknown'}; obsolete ${info.obsolete ?? 'unknown'}` }] : []),
|
|
146
|
+
...audit.findings.filter(f => f.paths.includes(item.sourcePath)).map(f => ({ source: f.code, detail: f.detail })),
|
|
147
|
+
],
|
|
148
|
+
uncertainty: ['Static inventory does not prove live loading, successful retrieval, or task outcomes',
|
|
149
|
+
...(!info?.createdAt ? ['Creation age is unknown; filesystem recency is not creation history'] : []),
|
|
150
|
+
...(info?.provenance !== 'verified' ? ['Provenance is unverified or conflicting'] : []),
|
|
151
|
+
...(!audit.coverage.complete ? ['Audit coverage is incomplete; absence is not evidence of disuse'] : [])],
|
|
152
|
+
blockedReasons: [...new Set(blocked)], protectedItemExceptions: exceptions,
|
|
153
|
+
expectedSavings: { residentTokens: tokens, estimator: attribution.estimator, confidence, basis },
|
|
154
|
+
retrievalImpact: { detail: kind === 'retire' ? 'Exclude from ordinary retrieval; preserve source, tombstone and explicit history/restore lookup'
|
|
155
|
+
: kind === 'conflict' || kind === 'supersede' ? 'Review source revisions and invalidate stale dependent summaries; never silently merge claims'
|
|
156
|
+
: 'Preserve source identity and authority; verify indexed discovery and bounded task-pack recall before resident removal',
|
|
157
|
+
requiresVerification: true, proposedPlacement: placement, category: kind === 'recategorize' ? info?.categoryCorrection ?? null : null,
|
|
158
|
+
taskPack: { builder: 'buildWorkspaceContextPack', totalCharacters: config.thresholds.taskPackCharacters } },
|
|
159
|
+
residentJustification: placement === 'resident' ? info?.residentProposal ?? null : null,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const seenDigests = new Set();
|
|
163
|
+
for (const item of [...audit.items].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
164
|
+
if (!item.record)
|
|
165
|
+
continue;
|
|
166
|
+
const id = item.id;
|
|
167
|
+
const record = item.record;
|
|
168
|
+
const info = metadata(id);
|
|
169
|
+
const attribution = costs.get(id);
|
|
170
|
+
const createdAge = age(info?.createdAt);
|
|
171
|
+
const reviewAge = age(info?.reviewedAt);
|
|
172
|
+
if (conflicts.has(id))
|
|
173
|
+
add(id, 'conflict', 'Conflicting claims or owner metadata need explicit source review', info?.conflictsWith ?? []);
|
|
174
|
+
if (info?.categoryCorrection)
|
|
175
|
+
add(id, 'recategorize', 'Owner evidence identifies a taxonomy correction; preserve placement, lifecycle and authority');
|
|
176
|
+
if (info?.summaryStale || info?.supersededBy || ['stale', 'superseded'].includes(item.trust)
|
|
177
|
+
|| item.reviewStatus === 'deployed-drift' || record.supersedes.some(handle => !items.has(handle) || items.get(handle)?.record?.lifecycle === 'active')) {
|
|
178
|
+
const related = info?.supersededBy ? [info.supersededBy] : record.supersedes;
|
|
179
|
+
add(id, 'supersede', 'Review supersession links and stale derived summaries against original source evidence', related, ['A reviewer must supply the replacement and validate lineage; no generated summary is accepted as a source fact']);
|
|
180
|
+
}
|
|
181
|
+
if (record.lifecycle !== 'active')
|
|
182
|
+
continue;
|
|
183
|
+
if (attribution.duplicateGroup && !seenDigests.has(attribution.duplicateGroup)) {
|
|
184
|
+
seenDigests.add(attribution.duplicateGroup);
|
|
185
|
+
const copies = audit.items.filter(i => i.digest === item.digest && i.record && i.id !== id);
|
|
186
|
+
const related = copies.map(i => i.id);
|
|
187
|
+
const incompatible = copies.some(i => i.record.scope !== record.scope || JSON.stringify(i.record.authority) !== JSON.stringify(record.authority));
|
|
188
|
+
add(id, 'merge', 'Identical content digests support consolidation review with immutable originals and source links retained', related, [...(incompatible ? ['Duplicate copies differ in scope or authority; no implicit merge is allowed'] : []),
|
|
189
|
+
...(related.some(key => conflicts.has(key)) ? ['A duplicate has conflicting source evidence; resolve the conflict first'] : []),
|
|
190
|
+
...(copies.some(copy => copy.record.lifecycle !== 'active') ? ['Duplicate inputs include inactive originals; review retained history separately'] : [])]);
|
|
191
|
+
}
|
|
192
|
+
if (!attribution.duplicateGroup && audit.findings.some(f => f.code === 'duplicate-resident-copy'
|
|
193
|
+
&& (f.paths.includes(item.sourcePath) || item.exposures.some(e => f.paths.includes(e.path))))) {
|
|
194
|
+
add(id, 'merge', 'Multiple physical resident copies share one immutable identity; consolidate exposure paths while retaining the canonical source', [], ['Copies already share one record identity; use the existing deployment/regeneration owner rather than merging a record with itself']);
|
|
195
|
+
}
|
|
196
|
+
if (record.placement === 'resident' && (attribution.tokens === null || attribution.tokens >= config.thresholds.demoteMinimumTokens)) {
|
|
197
|
+
add(id, 'demote', 'Review resident footprint against observed utility; keep indexed retrieval and prefer a bounded task pack', [], [...(attribution.utility.used !== false || !attribution.utility.evidence ? ['No explicit evidence of unused resident context'] : []),
|
|
198
|
+
...(info?.provenance !== 'verified' ? ['Verify provenance before demotion'] : [])], 'indexed');
|
|
199
|
+
}
|
|
200
|
+
if (record.placement === 'indexed' && attribution.utility.used === true && attribution.utility.evidence) {
|
|
201
|
+
add(id, 'promote', 'Successful-use evidence supports evaluating a bounded task pack before persistent residency', [], [], 'task-context');
|
|
202
|
+
}
|
|
203
|
+
if (info?.residentProposal) {
|
|
204
|
+
const proposal = info.residentProposal;
|
|
205
|
+
const reasons = [
|
|
206
|
+
...(!proposal.owner || proposal.owner !== record.authority.owner ? ['Always-loaded placement requires the existing owner'] : []),
|
|
207
|
+
...(!proposal.rationale ? ['Always-loaded placement requires a continuing rationale'] : []),
|
|
208
|
+
...(!proposal.budgetTokens ? ['Always-loaded placement requires a token budget'] : []),
|
|
209
|
+
...(!proposal.reviewDate || Date.parse(proposal.reviewDate) <= now ? ['Always-loaded placement requires a future review date'] : []),
|
|
210
|
+
...(attribution.tokens === null || !proposal.budgetTokens || attribution.tokens > proposal.budgetTokens ? ['Known footprint must fit the resident budget'] : []),
|
|
211
|
+
...(proposal.boundedPackEvaluated !== true ? ['Evaluate buildWorkspaceContextPack before persistent resident placement'] : []),
|
|
212
|
+
...(attribution.utility.used !== true || !attribution.utility.evidence ? ['Observed successful use is required'] : []),
|
|
213
|
+
...(info.provenance !== 'verified' ? ['Verify provenance before resident placement'] : []),
|
|
214
|
+
...(record.placement === 'resident' ? ['Item is already resident'] : []),
|
|
215
|
+
];
|
|
216
|
+
add(id, 'promote', 'Explicit always-loaded proposal requires owner, rationale, budget, review date and prior bounded-pack evaluation', [], reasons, 'resident');
|
|
217
|
+
const candidate = candidates.at(-1);
|
|
218
|
+
if (candidate.blockedReasons.length)
|
|
219
|
+
candidate.status = 'rejected';
|
|
220
|
+
}
|
|
221
|
+
// An old review opens investigation, never establishes retirement eligibility.
|
|
222
|
+
if (info?.obsolete || info?.supersededBy || (reviewAge !== null && reviewAge >= config.thresholds.reviewAgeDays)
|
|
223
|
+
|| attribution.utility.rejected === true || (attribution.utility.used === false && Boolean(attribution.utility.evidence))) {
|
|
224
|
+
add(id, 'retire', 'Review obsolete or superseded material; recency and frequency alone cannot justify retirement', [], [
|
|
225
|
+
...(info?.history !== 'known' ? ['Unknown history blocks retirement'] : []),
|
|
226
|
+
...(createdAge === null ? ['Unknown creation age blocks retirement'] : createdAge < config.thresholds.minimumAgeDays ? ['Freshly created notes are protected from retirement'] : []),
|
|
227
|
+
...(reviewAge !== null && reviewAge < 0 ? ['Review timestamp is in the future'] : []),
|
|
228
|
+
...(!info?.obsolete && !info?.supersededBy ? ['Age, frequency and rejection alone are insufficient; establish obsolete or superseded source evidence'] : []),
|
|
229
|
+
...(info?.provenance !== 'verified' ? ['Verified provenance is required for retirement'] : []),
|
|
230
|
+
...(attribution.utility.used === null || !attribution.utility.evidence ? ['Observed utility history is unknown'] : []),
|
|
231
|
+
...(attribution.utility.used === true ? ['Successful use contradicts retirement; resolve source evidence'] : []),
|
|
232
|
+
...(info?.supersededBy && !items.has(info.supersededBy) ? ['Successor is outside this inventory; verify retrieval and scope'] : []),
|
|
233
|
+
...(info?.supersededBy === id ? ['An item cannot supersede itself'] : []),
|
|
234
|
+
...(info?.supersededBy && items.has(info.supersededBy)
|
|
235
|
+
&& (items.get(info.supersededBy)?.record?.lifecycle !== 'active' || items.get(info.supersededBy)?.record?.scope !== record.scope)
|
|
236
|
+
? ['Successor must be active and retrievable in the same scope'] : []),
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const priority = { conflict: 0, recategorize: 1, supersede: 2, merge: 3, demote: 4, promote: 5, retire: 6 };
|
|
241
|
+
candidates.sort((a, b) => priority[a.kind] - priority[b.kind]
|
|
242
|
+
|| (b.expectedSavings.residentTokens ?? -1) - (a.expectedSavings.residentTokens ?? -1) || a.id.localeCompare(b.id));
|
|
243
|
+
candidates.forEach((candidate, index) => { candidate.advisoryRank = index + 1; });
|
|
244
|
+
return MemoryRecommendationsSchema.parse({
|
|
245
|
+
schemaVersion: 'aiwg.memory-recommendations.v1', at, workspace: audit.workspace, scope: audit.scope,
|
|
246
|
+
advisoryOnly: true, defaultPlacement: 'indexed', thresholds: config.thresholds,
|
|
247
|
+
coverage: { complete: audit.coverage.complete, limitsHit: audit.coverage.limitsHit, exclusions: audit.coverage.exclusions },
|
|
248
|
+
candidates, notes: [
|
|
249
|
+
'Ranking is advisory: conflicts, taxonomy, lineage, duplicates, demotion, promotion, retirement; savings only break ties. No approval or mutation is produced.',
|
|
250
|
+
'Thresholds are configurable local starting points to evaluate per workload, not research-established values.',
|
|
251
|
+
'New knowledge defaults to indexed storage; evaluate buildWorkspaceContextPack with the stated character budget before resident placement.',
|
|
252
|
+
'Owner plan validation, exact source review, retrieval verification and provider regeneration remain required. Savings are conditional and are not additive across overlapping candidates.',
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
export function formatMemoryRecommendations(report) {
|
|
257
|
+
return [`Memory recommendations (${report.scope}): ${report.workspace}; advisory only`,
|
|
258
|
+
...report.candidates.flatMap(c => [
|
|
259
|
+
`${c.advisoryRank}. ${c.kind} ${c.target.sourcePath}${c.retrievalImpact.proposedPlacement ? ` -> ${c.retrievalImpact.proposedPlacement}` : ''} [${c.status}] ${c.id}`,
|
|
260
|
+
` ${c.rationale}; resident savings ${c.expectedSavings.residentTokens ?? 'unknown'} tokens (${c.expectedSavings.confidence})`,
|
|
261
|
+
...c.evidence.map(e => ` Evidence: ${e.source}: ${e.detail}`),
|
|
262
|
+
...c.blockedReasons.map(reason => ` Blocked: ${reason}`),
|
|
263
|
+
...c.protectedItemExceptions.map(reason => ` Protected: ${reason}`),
|
|
264
|
+
` Retrieval: ${c.retrievalImpact.detail}; bounded pack ${c.retrievalImpact.taskPack.totalCharacters} characters`,
|
|
265
|
+
...(c.residentJustification ? [` Resident justification: ${JSON.stringify(c.residentJustification)}`] : []),
|
|
266
|
+
` Uncertainty: ${c.uncertainty.join('; ')}`,
|
|
267
|
+
]), ...report.notes].join('\n');
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=recommend.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { projectAiwgWritePath } from '../config/project-artifacts.js';
|
|
5
|
+
import { auditMemory } from './audit.js';
|
|
6
|
+
import { recommendMemory, RecommendationInputsSchema } from './recommend.js';
|
|
7
|
+
import { parseMemoryAuditOptions, resolveMemoryCostInputs } from './cli.js';
|
|
8
|
+
import { createMaintenanceOwners } from './maintenance-cli.js';
|
|
9
|
+
import { applyMaintenancePlan, MaintenanceApplyError, maintenanceOperationKey, maintenancePath, maintenancePlanDigest, previewMaintenancePlan, verifyMaintenancePlan } from './maintenance.js';
|
|
10
|
+
import { MaintenancePlanSchema } from './maintenance-contract.js';
|
|
11
|
+
/** Read-only owner views; the routed report is the only payload written. */
|
|
12
|
+
export async function reportScheduledMemory(kind, args, workspace, at) {
|
|
13
|
+
const options = parseMemoryAuditOptions(['scope', 'provider'].flatMap(key => args[key] ? [`--${key}`, args[key]] : []));
|
|
14
|
+
// Resolve before scanning: an unavailable external destination must fail closed.
|
|
15
|
+
const reportPath = projectAiwgWritePath(workspace, 'reports', 'memory', `${kind}-${randomUUID()}.json`);
|
|
16
|
+
const audit = await auditMemory({ ...options, workspace, at });
|
|
17
|
+
const report = kind === 'memory.recommend'
|
|
18
|
+
? recommendMemory(audit, args.evidence ? RecommendationInputsSchema.parse(JSON.parse(readFileSync(resolve(workspace, args.evidence), 'utf8'))) : {}, await resolveMemoryCostInputs({ demote: [], usage: args.usage ? resolve(workspace, args.usage) : undefined }))
|
|
19
|
+
: audit;
|
|
20
|
+
mkdirSync(dirname(reportPath), { recursive: true });
|
|
21
|
+
writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
22
|
+
const candidates = 'candidates' in report ? report.candidates.length : 0;
|
|
23
|
+
const findings = audit.findings.length;
|
|
24
|
+
// Findings are a normal report outcome, not a job failure: a non-zero exit
|
|
25
|
+
// would make the scheduler retry an unchanged read-only report.
|
|
26
|
+
return { exitStatus: 0,
|
|
27
|
+
summary: `${kind}: ${audit.items.length} items, ${findings} findings, ${candidates} recommendations`,
|
|
28
|
+
detail: { schemaVersion: report.schemaVersion, reportPath, counts: { items: audit.items.length, findings, candidates },
|
|
29
|
+
findingsStatus: findings || candidates ? 'findings' : 'clear', complete: report.coverage.complete } };
|
|
30
|
+
}
|
|
31
|
+
/** Scheduling executes one exact reviewed plan. It never prepares or resumes one. */
|
|
32
|
+
export async function applyScheduledMemory(args, root) {
|
|
33
|
+
try {
|
|
34
|
+
const plan = MaintenancePlanSchema.parse(JSON.parse(readFileSync(maintenancePath(root, args.plan), 'utf8')));
|
|
35
|
+
if (maintenancePlanDigest(plan) !== args.confirmedDigest)
|
|
36
|
+
throw new Error('Recorded plan digest mismatch; review and register the exact plan again');
|
|
37
|
+
const owners = await createMaintenanceOwners(root);
|
|
38
|
+
const receipts = await Promise.all(plan.operations.map(async (operation, index) => {
|
|
39
|
+
const owner = owners.find(value => value.name === operation.owner?.name);
|
|
40
|
+
if (!owner)
|
|
41
|
+
throw new Error('Unavailable maintenance owner');
|
|
42
|
+
const receipt = await owner.receipt(maintenanceOperationKey(plan, index), { readOnly: true });
|
|
43
|
+
if (receipt === undefined && await owner.pending?.(operation))
|
|
44
|
+
throw new Error('Interrupted owner transaction requires manual recovery');
|
|
45
|
+
return receipt;
|
|
46
|
+
}));
|
|
47
|
+
const completed = receipts.filter(receipt => receipt !== undefined).length;
|
|
48
|
+
if (completed && completed !== receipts.length)
|
|
49
|
+
throw new Error('Partial plan requires manual owner recovery; scheduling cannot replay it');
|
|
50
|
+
// Refuse invalid previews before even creating a coordinator journal.
|
|
51
|
+
// Apply repeats these checks, including the owners' checks under lock.
|
|
52
|
+
if (!completed)
|
|
53
|
+
await previewMaintenancePlan(plan, owners);
|
|
54
|
+
let applied = null;
|
|
55
|
+
let applyError;
|
|
56
|
+
let applyFailed = false;
|
|
57
|
+
if (completed !== receipts.length) {
|
|
58
|
+
try {
|
|
59
|
+
applied = await applyMaintenancePlan(plan, { root, owners, confirmedDigest: args.confirmedDigest });
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
applyFailed = true;
|
|
63
|
+
applyError = error;
|
|
64
|
+
if (error instanceof MaintenanceApplyError)
|
|
65
|
+
applied = error.receipt;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const verification = await verifyMaintenancePlan(plan, owners);
|
|
69
|
+
if (applyFailed || applied?.partial || verification.findings.length) {
|
|
70
|
+
return { exitStatus: 1, uncertain: true, summary: `Memory apply requires inspection of partial results or verification findings${applyError instanceof Error ? `: ${applyError.message}` : ''}`,
|
|
71
|
+
detail: { applied, verification } };
|
|
72
|
+
}
|
|
73
|
+
return { exitStatus: 0, summary: applied ? 'Reviewed memory plan applied and verified' : 'Reviewed memory plan already applied and verified; no changes',
|
|
74
|
+
detail: { planDigest: args.confirmedDigest, alreadyApplied: applied === null, applied, verification } };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return { exitStatus: 1, uncertain: true, summary: `Memory apply refused; inspect before recovery: ${error instanceof Error ? error.message : String(error)}`,
|
|
78
|
+
...(error instanceof MaintenanceApplyError ? { detail: { applied: error.receipt } } : {}) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=scheduled.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { dateTime } from '../schema/zod-validation.mjs';
|
|
3
|
+
export const MemoryScopeSchema = z.enum(['global', 'project', 'provider-native', 'loaded/session']);
|
|
4
|
+
export const MemoryPlacementSchema = z.enum(['resident', 'indexed', 'task-context', 'archived']);
|
|
5
|
+
export const MemoryLifecycleSchema = z.enum(['active', 'superseded', 'retired', 'quarantined', 'purged']);
|
|
6
|
+
export const MemoryAuthoritySchema = z.strictObject({
|
|
7
|
+
owner: z.string().min(1),
|
|
8
|
+
source: z.enum(['operator', 'reviewer', 'import', 'generated-output-proposed']),
|
|
9
|
+
protection: z.enum(['pinned', 'unprotected']),
|
|
10
|
+
});
|
|
11
|
+
export const MemoryObservationSchema = z.strictObject({
|
|
12
|
+
status: z.enum(['observed', 'inferred', 'on-disk', 'absent', 'inaccessible', 'opaque', 'unknown']),
|
|
13
|
+
evidence: z.strictObject({
|
|
14
|
+
source: z.string().min(1),
|
|
15
|
+
at: dateTime(),
|
|
16
|
+
session: z.string().min(1).nullable(),
|
|
17
|
+
provider: z.string().min(1).nullable(),
|
|
18
|
+
version: z.string().min(1).nullable(),
|
|
19
|
+
}),
|
|
20
|
+
}).superRefine((observation, context) => {
|
|
21
|
+
if (observation.status === 'observed'
|
|
22
|
+
&& (!observation.evidence.session || !observation.evidence.provider || !observation.evidence.version)) {
|
|
23
|
+
context.addIssue({ code: 'custom', message: 'Observed live context requires session, provider and version evidence' });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
export const MemoryTaxonomySchema = z.strictObject({
|
|
27
|
+
schemaVersion: z.literal('aiwg.memory-taxonomy.v1'),
|
|
28
|
+
scope: MemoryScopeSchema,
|
|
29
|
+
placement: MemoryPlacementSchema,
|
|
30
|
+
lifecycle: MemoryLifecycleSchema,
|
|
31
|
+
authority: MemoryAuthoritySchema,
|
|
32
|
+
observation: MemoryObservationSchema,
|
|
33
|
+
});
|
|
34
|
+
/** A taxonomy transition changes exactly the selected axis; it grants no mutation authority. */
|
|
35
|
+
export function transitionMemoryAxis(item, axis, value) {
|
|
36
|
+
const schemas = {
|
|
37
|
+
scope: MemoryScopeSchema,
|
|
38
|
+
placement: MemoryPlacementSchema,
|
|
39
|
+
lifecycle: MemoryLifecycleSchema,
|
|
40
|
+
authority: MemoryAuthoritySchema,
|
|
41
|
+
};
|
|
42
|
+
return { ...item, [axis]: schemas[axis].parse(value) };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=taxonomy.js.map
|
|
@@ -158,9 +158,9 @@ export function validateCanonicalMission(value) {
|
|
|
158
158
|
throw new Error('Canonical Mission artifacts must be an array.');
|
|
159
159
|
return structuredClone(value);
|
|
160
160
|
}
|
|
161
|
-
export function decodeMission(input, source) {
|
|
161
|
+
export function decodeMission(input, source, workerConsumer) {
|
|
162
162
|
const record = object(input);
|
|
163
|
-
assertMissionWorkerHandoff(record, source);
|
|
163
|
+
assertMissionWorkerHandoff(record, source, workerConsumer);
|
|
164
164
|
if (source === 'canonical') {
|
|
165
165
|
const value = validateCanonicalMission(record);
|
|
166
166
|
return { value, sourceVersion: MISSION_API_VERSION, warnings: [], preservedExtensions: structuredClone(value.extensions ?? {}), lossReport: [] };
|
|
@@ -227,7 +227,7 @@ function projectionLosses(value, target) {
|
|
|
227
227
|
return losses;
|
|
228
228
|
}
|
|
229
229
|
export function encodeMission(value, target, options = {}) {
|
|
230
|
-
assertMissionWorkerHandoff(value, target);
|
|
230
|
+
assertMissionWorkerHandoff(value, target, options.workerConsumer);
|
|
231
231
|
const mission = validateCanonicalMission(value);
|
|
232
232
|
if (target === 'canonical')
|
|
233
233
|
return { value: mission, targetVersion: MISSION_API_VERSION, warnings: [], lossReport: [] };
|
|
@@ -512,9 +512,17 @@ function gateRequest(gate) {
|
|
|
512
512
|
bundleDigest: gate.bundleDigest ?? workerDefinitionBundleDigest(),
|
|
513
513
|
};
|
|
514
514
|
}
|
|
515
|
+
/** Build registration metadata for the features explicitly qualified by an owner. */
|
|
516
|
+
export function workerConsumerSupport(features) {
|
|
517
|
+
for (const feature of features)
|
|
518
|
+
if (!WORKER_KNOWN_FEATURES.includes(feature)) {
|
|
519
|
+
throw new WorkerContractError('OG_CONTRACT_UNSUPPORTED', `OG_CONTRACT_UNSUPPORTED: unknown consumer feature '${feature}'.`);
|
|
520
|
+
}
|
|
521
|
+
return { revision: WORKER_CONTRACT_REVISION, bundleDigest: workerDefinitionBundleDigest(), features: [...features], lossless: true };
|
|
522
|
+
}
|
|
515
523
|
/** Trusted registration for the codec only; runtime consumers remain unqualified. */
|
|
516
524
|
function canonicalWorkerSupport() {
|
|
517
|
-
return
|
|
525
|
+
return workerConsumerSupport(WORKER_KNOWN_FEATURES);
|
|
518
526
|
}
|
|
519
527
|
function workerDispatchRecord(value) {
|
|
520
528
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -524,10 +532,10 @@ function workerDispatchRecord(value) {
|
|
|
524
532
|
}
|
|
525
533
|
/**
|
|
526
534
|
* Existing Mission/native owners call this before cloning, projection or launch.
|
|
527
|
-
* Only the canonical codec
|
|
528
|
-
*
|
|
535
|
+
* Only the canonical codec is qualified by default. Other owners must provide
|
|
536
|
+
* explicit support; an open extension map or consumer name is insufficient.
|
|
529
537
|
*/
|
|
530
|
-
export function assertMissionWorkerHandoff(input, consumer) {
|
|
538
|
+
export function assertMissionWorkerHandoff(input, consumer, workerConsumer) {
|
|
531
539
|
const seen = new Set();
|
|
532
540
|
const seenRecords = new Set();
|
|
533
541
|
function visitExtensions(value) {
|
|
@@ -540,8 +548,10 @@ export function assertMissionWorkerHandoff(input, consumer) {
|
|
|
540
548
|
if (dispatch.version !== WORKER_CONTRACT_REVISION) {
|
|
541
549
|
throw new WorkerContractError('OG_CONTRACT_UNSUPPORTED', `OG_CONTRACT_UNSUPPORTED: unsupported worker revision ${String(dispatch.version)}.`);
|
|
542
550
|
}
|
|
543
|
-
const support = canonicalWorkerSupport()
|
|
544
|
-
|
|
551
|
+
const support = workerConsumer ?? (consumer === 'canonical' ? canonicalWorkerSupport() : {
|
|
552
|
+
revision: WORKER_CONTRACT_REVISION, bundleDigest: workerDefinitionBundleDigest(), features: [], lossless: false,
|
|
553
|
+
});
|
|
554
|
+
assertWorkerConsumerSupported(support, {
|
|
545
555
|
requiredFeatures: Array.isArray(dispatch.requiredFeatures) ? dispatch.requiredFeatures : [...WORKER_BASELINE_FEATURES],
|
|
546
556
|
bundleDigest: workerDefinitionBundleDigest(),
|
|
547
557
|
});
|
|
@@ -12,7 +12,7 @@ import { homedir } from 'os';
|
|
|
12
12
|
import { existsSync } from 'fs';
|
|
13
13
|
import { findPackageRoot } from '../cli/find-package-root.js';
|
|
14
14
|
import { workerConfigDigest, validateWorkerGroupReferences } from '../config/aiwg-config.js';
|
|
15
|
-
import { DEFAULT_WORKER_SELECTION, mergeWorkerSelection, WorkerSelectionPolicySchema, validateUserProjectModelConfig } from './provider-policy.js';
|
|
15
|
+
import { DEFAULT_WORKER_SELECTION, WorkerRoleEffortError, mergeWorkerSelection, WorkerSelectionPolicySchema, validateUserProjectModelConfig } from './provider-policy.js';
|
|
16
16
|
/**
|
|
17
17
|
* Loads and merges model configurations
|
|
18
18
|
*/
|
|
@@ -126,11 +126,16 @@ export class ConfigLoader {
|
|
|
126
126
|
const parsed = JSON.parse(content);
|
|
127
127
|
const validation = validateUserProjectModelConfig(parsed);
|
|
128
128
|
if (!validation.valid) {
|
|
129
|
+
const missingEffort = validation.diagnostics.find(item => /^\/defaults\/workerSelection\/roleGuidance\/[a-z][a-z0-9-]*\/initialEffort$/.test(item.pointer ?? ''));
|
|
130
|
+
if (missingEffort)
|
|
131
|
+
throw new WorkerRoleEffortError(missingEffort.pointer.split('/')[4]);
|
|
129
132
|
throw new Error(validation.diagnostics.map(item => `${item.code}: ${item.message}`).join('; '));
|
|
130
133
|
}
|
|
131
134
|
return parsed;
|
|
132
135
|
}
|
|
133
136
|
catch (error) {
|
|
137
|
+
if (error instanceof WorkerRoleEffortError)
|
|
138
|
+
throw error;
|
|
134
139
|
if (error.code === 'ENOENT')
|
|
135
140
|
return null;
|
|
136
141
|
throw new Error(`Invalid user model config ${this.locations.userConfig}: ${error}`);
|
|
@@ -145,11 +150,16 @@ export class ConfigLoader {
|
|
|
145
150
|
const parsed = JSON.parse(content);
|
|
146
151
|
const validation = validateUserProjectModelConfig(parsed);
|
|
147
152
|
if (!validation.valid) {
|
|
153
|
+
const missingEffort = validation.diagnostics.find(item => /^\/defaults\/workerSelection\/roleGuidance\/[a-z][a-z0-9-]*\/initialEffort$/.test(item.pointer ?? ''));
|
|
154
|
+
if (missingEffort)
|
|
155
|
+
throw new WorkerRoleEffortError(missingEffort.pointer.split('/')[4]);
|
|
148
156
|
throw new Error(validation.diagnostics.map(item => `${item.code}: ${item.message}`).join('; '));
|
|
149
157
|
}
|
|
150
158
|
return parsed;
|
|
151
159
|
}
|
|
152
160
|
catch (error) {
|
|
161
|
+
if (error instanceof WorkerRoleEffortError)
|
|
162
|
+
throw error;
|
|
153
163
|
if (error.code === 'ENOENT')
|
|
154
164
|
return null;
|
|
155
165
|
throw new Error(`Invalid project model config ${this.locations.projectConfig}: ${error}`);
|