archctx-contracts 0.8.0 → 0.8.1
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/fixtures/valid/archctx-capabilities.json +1 -1
- package/fixtures/valid/product-version-manifest.json +5 -5
- package/package.json +1 -1
- package/schemas/runtime/native-model-issue.schema.json +18 -0
- package/src/failure.ts +37 -3
- package/src/native-context.ts +31 -29
- package/src/ports.ts +4 -0
- package/src/product-version.ts +1 -1
- package/src/schema.ts +2 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "archcontext.capabilities/v1",
|
|
3
|
-
"package": { "name": "archctx", "version": "0.8.
|
|
3
|
+
"package": { "name": "archctx", "version": "0.8.1" },
|
|
4
4
|
"protocols": {
|
|
5
5
|
"projectionRequest": "archcontext.projection-request/v1",
|
|
6
6
|
"projectionResult": "archcontext.projection-result/v2",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": "archcontext.product-version-manifest/v1",
|
|
3
3
|
"product": {
|
|
4
4
|
"name": "archctx",
|
|
5
|
-
"version": "0.8.
|
|
5
|
+
"version": "0.8.1",
|
|
6
6
|
"distribution": "one-package"
|
|
7
7
|
},
|
|
8
8
|
"packageManager": "bun@1.4.3",
|
|
@@ -12,25 +12,25 @@
|
|
|
12
12
|
"surfaces": {
|
|
13
13
|
"cli": {
|
|
14
14
|
"packageName": "@archcontext/surfaces",
|
|
15
|
-
"version": "0.8.
|
|
15
|
+
"version": "0.8.1",
|
|
16
16
|
"entrypoint": "archctx"
|
|
17
17
|
},
|
|
18
18
|
"daemon": {
|
|
19
19
|
"packageName": "@archcontext/local-runtime",
|
|
20
|
-
"version": "0.8.
|
|
20
|
+
"version": "0.8.1",
|
|
21
21
|
"entrypoint": "archctx daemon start",
|
|
22
22
|
"rpcSchemaVersion": "archcontext.runtime-rpc/v2"
|
|
23
23
|
},
|
|
24
24
|
"mcp": {
|
|
25
25
|
"packageName": "@archcontext/surfaces",
|
|
26
|
-
"version": "0.8.
|
|
26
|
+
"version": "0.8.1",
|
|
27
27
|
"entrypoint": "archctx mcp",
|
|
28
28
|
"transport": "stdio"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"schemas": {
|
|
32
32
|
"schemaSetVersion": "2026-06-25.al0-ledger",
|
|
33
|
-
"contractsPackageVersion": "0.8.
|
|
33
|
+
"contractsPackageVersion": "0.8.1",
|
|
34
34
|
"reviewChallenge": "archcontext.review-challenge/v2",
|
|
35
35
|
"attestation": "archcontext.attestation/v2",
|
|
36
36
|
"deviceIdentity": "archcontext.device-identity/v1",
|
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://archctx.repoharness.com/schemas/runtime/native-model-issue.schema.json",
|
|
4
|
+
"title": "NativeModelIssue",
|
|
5
|
+
"description": "A native model schema or reference issue. Source paths are optional for callers that do not supply parsed record paths.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["entityId", "field", "invariant"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"entityId": { "type": "string" },
|
|
11
|
+
"field": { "type": "string" },
|
|
12
|
+
"invariant": { "type": "string" },
|
|
13
|
+
"path": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "The repository-relative path of the parsed source file that holds this record. It is not derived from the entity ID."
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/failure.ts
CHANGED
|
@@ -23,6 +23,16 @@ export interface FailureEnvelope<C extends FailureCode = FailureCode> {
|
|
|
23
23
|
error: StrictFailure<C>;
|
|
24
24
|
}
|
|
25
25
|
type Policy = { code: FailureCode; message: string; fields?: readonly string[]; action?: string; facts?: Readonly<Record<string, string>>; native?: boolean; recovery?: boolean; journalRecovery?: boolean; retryable?: boolean };
|
|
26
|
+
/** Closed repository diagnostics. Messages contain no process output or filesystem paths. */
|
|
27
|
+
export const CODE_FACTS_ISSUE_MESSAGES = Object.freeze({
|
|
28
|
+
"not-initialized": "CodeGraph repository status is not initialized",
|
|
29
|
+
"version-mismatch": "CodeGraph repository status version mismatch",
|
|
30
|
+
"project-path-mismatch": "CodeGraph repository project path mismatch",
|
|
31
|
+
"index-incomplete": "CodeGraph repository index is not complete and compatible",
|
|
32
|
+
"extraction-version-mismatch": "CodeGraph repository extraction version mismatch",
|
|
33
|
+
"worktree-mismatch": "CodeGraph repository worktree mismatch",
|
|
34
|
+
"pending-source-changes": "CodeGraph repository index has pending or unreported source changes"
|
|
35
|
+
});
|
|
26
36
|
/** Each reason has a closed fact vocabulary. No source text or recovery binding is a summary fact. */
|
|
27
37
|
const policies: Record<string, Policy> = {
|
|
28
38
|
"git-worktree-required": { code: "AC_REPO_NOT_FOUND", message: "This operation requires a Git worktree.", fields: ["checks"] },
|
|
@@ -33,6 +43,7 @@ const policies: Record<string, Policy> = {
|
|
|
33
43
|
"projection-manifest-invalid": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest violates its contract." },
|
|
34
44
|
"projection-footprint-uncovered": { code: "AC_PRECONDITION_FAILED", message: "Declared source footprints lack a projection target." },
|
|
35
45
|
"index-missing": { code: "AC_CODE_FACTS_UNAVAILABLE", message: "Required code facts have no available index. Run codegraph init." },
|
|
46
|
+
"code-facts-not-ready": { code: "AC_CODE_FACTS_NOT_READY", message: "Repository code facts are not ready. Run archctx doctor --json and follow the reported action.", facts: { issues: "codeFactsIssues" } },
|
|
36
47
|
"checkpoint-input-invalid": { code: "AC_SCHEMA_INVALID", message: "Checkpoint arguments violate the declared input schema.", fields: ["issueCount"], action: "correct-command-arguments" },
|
|
37
48
|
"cli-input-invalid": { code: "AC_SCHEMA_INVALID", message: "Command arguments violate the declared input contract.", action: "correct-command-arguments" },
|
|
38
49
|
"complete-input-invalid": { code: "AC_SCHEMA_INVALID", message: "Completion input violates the caller authority contract.", action: "correct-command-arguments" },
|
|
@@ -41,7 +52,7 @@ const policies: Record<string, Policy> = {
|
|
|
41
52
|
"runtime-version-unsupported": { code: "AC_RUNTIME_VERSION_UNSUPPORTED", message: "The connected runtime version is unsupported." },
|
|
42
53
|
"rpc-timeout": { code: "RPC_TIMEOUT", message: "The runtime RPC deadline elapsed without a response; a mutation outcome is unknown.", fields: ["method", "timeoutMs", "elapsedMs", "mutationOutcome"], action: "reconcile-before-retry" },
|
|
43
54
|
"rpc-aborted": { code: "RPC_ABORTED", message: "The runtime RPC caller aborted without a response; a mutation outcome is unknown.", fields: ["method", "timeoutMs", "elapsedMs", "mutationOutcome"], action: "reconcile-before-retry" },
|
|
44
|
-
"failure-unclassified": { code: "AC_UNCLASSIFIED_FAILURE", message: "The operation failed without a classified cause.
|
|
55
|
+
"failure-unclassified": { code: "AC_UNCLASSIFIED_FAILURE", message: "The operation failed without a classified cause. Run archctx doctor --json before retrying.", action: "inspect-diagnostics" },
|
|
45
56
|
};
|
|
46
57
|
// C1 policies are selected at explicit producer branches, never from diagnostic text.
|
|
47
58
|
Object.assign(policies, {
|
|
@@ -278,7 +289,7 @@ for (const policy of Object.values(policies)) {
|
|
|
278
289
|
}
|
|
279
290
|
export const FAILURE_POLICIES: Readonly<Record<string, Readonly<Policy>>> = Object.freeze(policies);
|
|
280
291
|
const catalogDefaults = Object.fromEntries(Object.entries(ERROR_CATALOG).map(([code, value]) => [code, Object.freeze({ severity: value.severity, retryable: value.retryable, action: value.action })])) as Readonly<Record<ArchContextErrorCode, Readonly<Pick<StrictFailure, "severity" | "retryable" | "action">>>>;
|
|
281
|
-
const actions = new Set([...Object.values(catalogDefaults).map(value => value.action), "correct-command-arguments", "inspect-diagnostics", "reconcile-before-retry", ...Object.values(policies).flatMap(policy => policy.action ? [policy.action] : [])]);
|
|
292
|
+
const actions = new Set([...Object.values(catalogDefaults).map(value => value.action), "correct-command-arguments", "inspect-diagnostics", "reconcile-before-retry", "install-compatible-codegraph", ...Object.values(policies).flatMap(policy => policy.action ? [policy.action] : [])]);
|
|
282
293
|
const bytes = (value: string) => new TextEncoder().encode(value).byteLength;
|
|
283
294
|
const record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
284
295
|
const finiteNonnegative = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
@@ -400,6 +411,20 @@ const localEnums: Readonly<Record<string, readonly string[]>> = {
|
|
|
400
411
|
]
|
|
401
412
|
};
|
|
402
413
|
function localFactValue(rule: string, value: unknown): Json {
|
|
414
|
+
if (rule === "codeFactsIssues") {
|
|
415
|
+
if (!Array.isArray(value)) throw new Error("Code facts issues must be a list");
|
|
416
|
+
const length = Object.getOwnPropertyDescriptor(value, "length")?.value;
|
|
417
|
+
if (!Number.isSafeInteger(length) || length < 1 || length > 32 || Reflect.ownKeys(value).length !== length + 1) throw new Error("Code facts issues exceed their closed budget");
|
|
418
|
+
return Array.from({ length }, (_, index) => {
|
|
419
|
+
const entry = Object.getOwnPropertyDescriptor(value, String(index));
|
|
420
|
+
if (!entry || !("value" in entry)) throw new Error("Code facts issues must use own data");
|
|
421
|
+
const fields = dataRecord(entry.value);
|
|
422
|
+
const code = fields.code?.value, message = fields.message?.value;
|
|
423
|
+
if (Object.keys(fields).length !== 2 || typeof code !== "string" || !Object.hasOwn(CODE_FACTS_ISSUE_MESSAGES, code)
|
|
424
|
+
|| message !== CODE_FACTS_ISSUE_MESSAGES[code as keyof typeof CODE_FACTS_ISSUE_MESSAGES]) throw new Error("Unknown code facts diagnostic");
|
|
425
|
+
return { code, message };
|
|
426
|
+
});
|
|
427
|
+
}
|
|
403
428
|
if (rule === "commandSuggestion" && (value === null || (typeof value === "string" && bytes(value) <= 1024 && /^archctx (recommendations |refactor verify )[^\r\n]*$/.test(value)))) return value;
|
|
404
429
|
if (rule === "publicConfirmationToken" && typeof value === "string" && bytes(value) <= 1024 && /^public:github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+:[a-f0-9]{40}:[A-Za-z0-9_.:-]+$/.test(value)) return value;
|
|
405
430
|
if (rule === "diagnosticPaths" || rule === "diagnosticCommands") {
|
|
@@ -568,7 +593,7 @@ function nativeFactsFor(policy: Policy, input: unknown): {[key: string]: Json} {
|
|
|
568
593
|
if (type === "diagnostic") { const s = text(value); if (bytes(s) > 256) { omit(s); return undefined; } return s; }
|
|
569
594
|
const item = dataRecord(value);
|
|
570
595
|
if (type === "issues") {
|
|
571
|
-
if (
|
|
596
|
+
if (!["entityId", "field", "invariant"].every(key => Object.hasOwn(item, key)) || Object.keys(item).some(key => !["entityId", "field", "invariant", "path"].includes(key))) throw new Error("Native issue schema is invalid");
|
|
572
597
|
const clean = Object.fromEntries(Object.entries(item).map(([key, data]) => [key, text(data.value)]));
|
|
573
598
|
if (Object.values(clean).some(v => bytes(v) > 256)) { omit(clean); return undefined; }
|
|
574
599
|
return clean;
|
|
@@ -616,12 +641,21 @@ function nativeFactsFor(policy: Policy, input: unknown): {[key: string]: Json} {
|
|
|
616
641
|
}
|
|
617
642
|
result[key] = values;
|
|
618
643
|
}
|
|
644
|
+
const sourceIssues = Array.isArray(result.issues) ? result.issues as Array<Record<string, Json>> : [];
|
|
645
|
+
// Legacy issues without source paths keep any explicit path supplied by the caller.
|
|
646
|
+
const summarizePath = sourceIssues.some(issue => typeof issue.path === "string");
|
|
619
647
|
const counts = () => {
|
|
620
648
|
if (!Number.isSafeInteger(omitted) || !Number.isSafeInteger(characters)) throw new Error("Native diagnostic counts exceed schema");
|
|
621
649
|
if (omitted) result.omittedDiagnosticCount = omitted;
|
|
622
650
|
if (characters) result.omittedDiagnosticCharacters = characters;
|
|
623
651
|
};
|
|
624
652
|
const fits = () => {
|
|
653
|
+
// Derive the summary from surviving whole issues, including after budget omission.
|
|
654
|
+
if (summarizePath) {
|
|
655
|
+
const path = sourceIssues[0]?.path;
|
|
656
|
+
if (typeof path === "string" && sourceIssues.every(issue => issue.path === path)) result.path = path;
|
|
657
|
+
else delete result.path;
|
|
658
|
+
}
|
|
625
659
|
counts();
|
|
626
660
|
const base = catalogDefaults[policy.code as ArchContextErrorCode];
|
|
627
661
|
const error = {code: policy.code, message: policy.message, severity: base.severity, retryable: policy.retryable ?? base.retryable, action: policy.action ?? base.action, reasonCode: Object.entries(policies).find(([, p]) => p === policy)?.[0], details: result};
|
package/src/native-context.ts
CHANGED
|
@@ -149,7 +149,7 @@ export function nativeByteCompare(left: string, right: string): number {
|
|
|
149
149
|
|
|
150
150
|
type SchemaDocument = Parameters<typeof validateJsonSchema>[0];
|
|
151
151
|
export interface NativeContextSchemas { node: SchemaDocument; relation: SchemaDocument; flow: SchemaDocument }
|
|
152
|
-
export interface NativeModelIssue { entityId: string; field: string; invariant: string }
|
|
152
|
+
export interface NativeModelIssue { entityId: string; field: string; invariant: string; path?: string }
|
|
153
153
|
|
|
154
154
|
/** Fixed canonical documents are supplied by the bundled reader, never by repository input. */
|
|
155
155
|
export function nativeContextSchemaResolver(schemas: NativeContextSchemas): JsonSchemaResolver {
|
|
@@ -162,10 +162,14 @@ export function nativeAllowedNodeKinds(schemas: NativeContextSchemas): string[]
|
|
|
162
162
|
return [...kinds] as string[];
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/** Complete native shape/reference validation;
|
|
166
|
-
export function nativeContextModelIssues(model: NativeContextModel, schemas: NativeContextSchemas): NativeModelIssue[] {
|
|
165
|
+
/** Complete native shape/reference validation; optional paths bind parsed records, never IDs. */
|
|
166
|
+
export function nativeContextModelIssues(model: NativeContextModel, schemas: NativeContextSchemas, recordPaths?: ReadonlyMap<NativeModelRecord, string>): NativeModelIssue[] {
|
|
167
167
|
const issues: NativeModelIssue[] = [];
|
|
168
|
-
const add = (
|
|
168
|
+
const add = (record: NativeModelRecord | string, field: string, invariant: string) => {
|
|
169
|
+
const entityId = typeof record === "string" ? record : typeof record.id === "string" ? record.id : "(undeclared)";
|
|
170
|
+
const path = typeof record === "string" ? undefined : recordPaths?.get(record);
|
|
171
|
+
issues.push({ entityId, field, invariant, ...(path === undefined ? {} : { path }) });
|
|
172
|
+
};
|
|
169
173
|
const resolveSchema = nativeContextSchemaResolver(schemas);
|
|
170
174
|
const ids = new Set<string>();
|
|
171
175
|
for (const [kind, records] of [["node", model.nodes], ["relation", model.relations], ["flow", model.flows]] as const) {
|
|
@@ -182,46 +186,45 @@ export function nativeContextModelIssues(model: NativeContextModel, schemas: Nat
|
|
|
182
186
|
const item = pending.pop()!;
|
|
183
187
|
const value = item.value;
|
|
184
188
|
if (item.exit) { active.delete(value as object); done.add(value as object); continue; }
|
|
185
|
-
if (typeof value === "number" && !Number.isFinite(value)) { add(
|
|
189
|
+
if (typeof value === "number" && !Number.isFinite(value)) { add(record, item.field, "non-json-number"); jsonInvalid = true; break; }
|
|
186
190
|
if (value === null || typeof value !== "object") continue;
|
|
187
|
-
if (active.has(value)) { add(
|
|
191
|
+
if (active.has(value)) { add(record, item.field, "non-json-cycle"); jsonInvalid = true; break; }
|
|
188
192
|
if (done.has(value)) continue;
|
|
189
193
|
const prototype = Object.getPrototypeOf(value);
|
|
190
|
-
if (Array.isArray(value) ? prototype !== Array.prototype : prototype !== Object.prototype) { add(
|
|
194
|
+
if (Array.isArray(value) ? prototype !== Array.prototype : prototype !== Object.prototype) { add(record, item.field, "non-json-prototype"); jsonInvalid = true; break; }
|
|
191
195
|
active.add(value); pending.push({ value, field: item.field, exit: true });
|
|
192
196
|
for (const [key, child] of Object.entries(value)) pending.push({ value: child, field: `${item.field}.${key}` });
|
|
193
197
|
}
|
|
194
198
|
if (jsonInvalid) continue;
|
|
195
199
|
const schema = kind === "node" ? schemas.node : kind === "relation" ? schemas.relation : schemas.flow;
|
|
196
|
-
for (const issue of validateJsonSchema(schema, record, { resolveSchema }).issues) add(
|
|
197
|
-
if (ids.has(id)) add(
|
|
200
|
+
for (const issue of validateJsonSchema(schema, record, { resolveSchema }).issues) add(record, issue.path, issue.message);
|
|
201
|
+
if (ids.has(id)) add(record, "id", "duplicate-entity-id");
|
|
198
202
|
ids.add(id);
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
// Invalid shapes cannot safely participate in reference validation.
|
|
202
206
|
if (issues.length > 0) return issues;
|
|
203
207
|
for (const kind of ["nodes", "relations", "flows"] as const) {
|
|
204
|
-
const expected = new
|
|
205
|
-
for (const id of expected) if (!Object.hasOwn(model.sourcePaths[kind], id)) add(
|
|
208
|
+
const expected = new Map(model[kind].map(entity => [entity.id as string, entity]));
|
|
209
|
+
for (const [id, entity] of expected) if (!Object.hasOwn(model.sourcePaths[kind], id)) add(entity, `sourcePaths.${kind}`, "source-path-index-incomplete");
|
|
206
210
|
for (const [id, path] of Object.entries(model.sourcePaths[kind])) {
|
|
207
211
|
if (!expected.has(id)) add(id, `sourcePaths.${kind}`, "source-path-index-unknown-entity");
|
|
208
|
-
if (!isNativeContextPath(path)) add(id, `sourcePaths.${kind}`, "path-unsafe");
|
|
212
|
+
if (!isNativeContextPath(path)) add(expected.get(id) ?? id, `sourcePaths.${kind}`, "path-unsafe");
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
const nodes = new Map(model.nodes.map(node => [node.id as string, node]));
|
|
212
216
|
const checked = new Set<string>();
|
|
213
217
|
for (const node of model.nodes) {
|
|
214
|
-
const id = node.id as string;
|
|
215
218
|
const chain = new Set<string>();
|
|
216
219
|
let cursor: NativeModelRecord | undefined = node;
|
|
217
220
|
while (cursor) {
|
|
218
221
|
const current = cursor.id as string;
|
|
219
|
-
if (chain.has(current)) { add(
|
|
222
|
+
if (chain.has(current)) { add(node, "parent", "parent-cycle"); break; }
|
|
220
223
|
if (checked.has(current)) break;
|
|
221
224
|
chain.add(current);
|
|
222
225
|
if (cursor.parent === undefined) break;
|
|
223
226
|
const parent = nodes.get(cursor.parent as string);
|
|
224
|
-
if (!parent) { add(
|
|
227
|
+
if (!parent) { add(cursor, "parent", "parent-node-missing"); break; }
|
|
225
228
|
cursor = parent;
|
|
226
229
|
}
|
|
227
230
|
for (const current of chain) checked.add(current);
|
|
@@ -229,48 +232,47 @@ export function nativeContextModelIssues(model: NativeContextModel, schemas: Nat
|
|
|
229
232
|
const selectors = new Set<string>();
|
|
230
233
|
for (const node of model.nodes) {
|
|
231
234
|
const source = node.source as { include?: string[]; exclude?: string[]; entrypoints?: Array<{ id: string; path: string; symbols: Array<{ name: string; sinks: Array<{ id: string; path: string; symbol: string }> }> }> } | undefined;
|
|
232
|
-
for (const path of [...source?.include ?? [], ...source?.exclude ?? []]) if (!isNativeContextPath(path)) add(node
|
|
235
|
+
for (const path of [...source?.include ?? [], ...source?.exclude ?? []]) if (!isNativeContextPath(path)) add(node, "source", "path-unsafe");
|
|
233
236
|
for (const entry of source?.entrypoints ?? []) {
|
|
234
|
-
if (!isNativeContextPath(entry.path)) add(node
|
|
237
|
+
if (!isNativeContextPath(entry.path)) add(node, "source.entrypoints.path", "path-unsafe");
|
|
235
238
|
for (const symbol of entry.symbols) {
|
|
236
239
|
for (const sink of symbol.sinks) {
|
|
237
|
-
if (!isNativeContextPath(sink.path)) add(node
|
|
240
|
+
if (!isNativeContextPath(sink.path)) add(node, "source.entrypoints.symbols.sinks.path", "path-unsafe");
|
|
238
241
|
const selector = JSON.stringify([entry.id, symbol.name, sink.id]);
|
|
239
|
-
if (selectors.has(selector)) add(node
|
|
242
|
+
if (selectors.has(selector)) add(node, "source.entrypoints.symbols.sinks.id", "selector-ambiguous");
|
|
240
243
|
selectors.add(selector);
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
247
|
}
|
|
245
248
|
for (const relation of model.relations) {
|
|
246
|
-
for (const field of ["source", "target", "via"] as const) if (relation[field] !== undefined && !nodes.has(relation[field] as string)) add(relation
|
|
249
|
+
for (const field of ["source", "target", "via"] as const) if (relation[field] !== undefined && !nodes.has(relation[field] as string)) add(relation, field, "relation-node-missing");
|
|
247
250
|
}
|
|
248
251
|
for (const flow of model.flows) {
|
|
249
|
-
|
|
250
|
-
if (nodes.get(flow.capabilityId as string)?.kind !== "capability") add(id, "capabilityId", "flow-capability-missing");
|
|
252
|
+
if (nodes.get(flow.capabilityId as string)?.kind !== "capability") add(flow, "capabilityId", "flow-capability-missing");
|
|
251
253
|
if (flow.applicability !== "required") continue;
|
|
252
254
|
const participants = flow.participants as Array<{ id: string; nodeId: string }>;
|
|
253
255
|
const participantIds = new Set<string>();
|
|
254
256
|
for (const participant of participants) {
|
|
255
|
-
if (participantIds.has(participant.id)) add(
|
|
257
|
+
if (participantIds.has(participant.id)) add(flow, "participants.id", "participant-id-ambiguous");
|
|
256
258
|
participantIds.add(participant.id);
|
|
257
|
-
if (!nodes.has(participant.nodeId)) add(
|
|
259
|
+
if (!nodes.has(participant.nodeId)) add(flow, "participants.nodeId", "participant-node-missing");
|
|
258
260
|
}
|
|
259
261
|
const definitions = new Map<string, string>();
|
|
260
262
|
const outcomes = flow.outcomes as Array<{ id: string; steps: NativeModelRecord[]; terminal: { participant: string } }>;
|
|
261
263
|
const outcomeIds = new Set<string>();
|
|
262
264
|
for (const outcome of outcomes) {
|
|
263
|
-
if (outcomeIds.has(outcome.id)) add(
|
|
265
|
+
if (outcomeIds.has(outcome.id)) add(flow, "outcomes.id", "outcome-id-ambiguous");
|
|
264
266
|
outcomeIds.add(outcome.id);
|
|
265
|
-
if (!participantIds.has(outcome.terminal.participant)) add(
|
|
267
|
+
if (!participantIds.has(outcome.terminal.participant)) add(flow, "outcomes.terminal", "terminal-participant-missing");
|
|
266
268
|
}
|
|
267
269
|
for (const step of [...flow.steps as NativeModelRecord[], ...outcomes.flatMap(outcome => outcome.steps)]) {
|
|
268
|
-
if (!participantIds.has(step.from as string) || !participantIds.has(step.to as string)) add(
|
|
270
|
+
if (!participantIds.has(step.from as string) || !participantIds.has(step.to as string)) add(flow, "steps", "step-participant-missing");
|
|
269
271
|
const definition = canonicalize(step);
|
|
270
|
-
if (definitions.has(step.id as string) && definitions.get(step.id as string) !== definition) add(
|
|
272
|
+
if (definitions.has(step.id as string) && definitions.get(step.id as string) !== definition) add(flow, "steps.id", "step-definition-ambiguous");
|
|
271
273
|
definitions.set(step.id as string, definition);
|
|
272
274
|
const selector = step.evidence as { entrypointId: string; sourceSymbol: string; sinkId: string };
|
|
273
|
-
if (!selectors.has(JSON.stringify([selector.entrypointId, selector.sourceSymbol, selector.sinkId]))) add(
|
|
275
|
+
if (!selectors.has(JSON.stringify([selector.entrypointId, selector.sourceSymbol, selector.sinkId]))) add(flow, "steps.evidence", "selector-missing");
|
|
274
276
|
}
|
|
275
277
|
}
|
|
276
278
|
return issues;
|
package/src/ports.ts
CHANGED
|
@@ -121,6 +121,8 @@ export interface SourceSelector {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export interface CodeFactsPort {
|
|
124
|
+
/** Read-only compatibility check before the existing incremental sync. No retry or rebuild. */
|
|
125
|
+
assertCanSync?(workspaceRoot: string): void;
|
|
124
126
|
ensureReady(workspace: WorkspaceRef): Promise<CodeFactsSnapshot>;
|
|
125
127
|
sync(input: { workspace: WorkspaceRef; changedPaths?: string[] }): Promise<CodeFactsSnapshot>;
|
|
126
128
|
buildTaskContext(input: { task: string; maxSymbols: number; includeSource: boolean; changedPaths?: string[] }): Promise<NormalizedCodeContext>;
|
|
@@ -148,6 +150,8 @@ export interface ModelValidationResult {
|
|
|
148
150
|
* `appliesTo` id with no node. Omitted when empty.
|
|
149
151
|
*/
|
|
150
152
|
referenceErrors?: string[];
|
|
153
|
+
/** Node schema errors with their file paths. Only a write to that node can repair them. */
|
|
154
|
+
nodeSchemaErrors?: { path: string; error: string }[];
|
|
151
155
|
/** Problems that do not invalidate the model, such as an ignored legacy setting. Omitted when empty. */
|
|
152
156
|
warnings?: string[];
|
|
153
157
|
modelDigest: string;
|
package/src/product-version.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const ARCHCONTEXT_PRODUCT_NAME = "archctx";
|
|
2
|
-
export const ARCHCONTEXT_PRODUCT_VERSION = "0.8.
|
|
2
|
+
export const ARCHCONTEXT_PRODUCT_VERSION = "0.8.1";
|
|
3
3
|
export const ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3";
|
|
4
4
|
export const ARCHCONTEXT_NODE_RANGE = ">=22.22 <26";
|
|
5
5
|
export const LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v2";
|
package/src/schema.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type ArchContextErrorCode =
|
|
|
18
18
|
| "AC_NODE_VERSION_UNSUPPORTED"
|
|
19
19
|
| "AC_CODEGRAPH_UNAVAILABLE"
|
|
20
20
|
| "AC_CODE_FACTS_UNAVAILABLE"
|
|
21
|
+
| "AC_CODE_FACTS_NOT_READY"
|
|
21
22
|
| "AC_INDEX_STALE"
|
|
22
23
|
| "AC_CONTEXT_STALE"
|
|
23
24
|
| "AC_SCHEMA_INVALID"
|
|
@@ -68,6 +69,7 @@ export const ERROR_CATALOG: Record<ArchContextErrorCode, Omit<ArchContextError,
|
|
|
68
69
|
AC_NODE_VERSION_UNSUPPORTED: { code: "AC_NODE_VERSION_UNSUPPORTED", severity: "error", retryable: false, action: "use-supported-node-version" },
|
|
69
70
|
AC_CODEGRAPH_UNAVAILABLE: { code: "AC_CODEGRAPH_UNAVAILABLE", severity: "error", retryable: true, action: "run-diagnostics" },
|
|
70
71
|
AC_CODE_FACTS_UNAVAILABLE: { code: "AC_CODE_FACTS_UNAVAILABLE", severity: "error", retryable: true, action: "codegraph-init" },
|
|
72
|
+
AC_CODE_FACTS_NOT_READY: { code: "AC_CODE_FACTS_NOT_READY", severity: "error", retryable: false, action: "codegraph-index" },
|
|
71
73
|
AC_INDEX_STALE: { code: "AC_INDEX_STALE", severity: "warning", retryable: true, action: "sync-codefacts" },
|
|
72
74
|
AC_CONTEXT_STALE: { code: "AC_CONTEXT_STALE", severity: "warning", retryable: true, action: "prepare-task-again" },
|
|
73
75
|
AC_SCHEMA_INVALID: { code: "AC_SCHEMA_INVALID", severity: "error", retryable: false, action: "repair-model" },
|