archctx-contracts 0.7.2 → 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/invalid/hook-debt-checkpoint-failed-read.json +54 -0
- package/fixtures/invalid/native-context-invalid-model-summary.json +635 -0
- package/fixtures/invalid/native-context-missing-contract-bytes.json +636 -0
- package/fixtures/invalid/native-context-missing-input-bytes.json +636 -0
- package/fixtures/invalid/native-context-mixed-snapshot.json +637 -0
- package/fixtures/invalid/projection-result-applied-incomplete-coverage.json +78 -0
- package/fixtures/invalid/refactor-decision-open-status.json +38 -0
- package/fixtures/valid/archctx-capabilities.json +3 -3
- package/fixtures/valid/capability-dependencies.json +1 -1
- package/fixtures/valid/docs-anchors.json +2 -1
- package/fixtures/valid/flow-selector-check.json +1 -1
- package/fixtures/valid/hook-debt-checkpoint.json +54 -0
- package/fixtures/valid/native-context-export.json +636 -0
- package/fixtures/valid/product-version-manifest.json +8 -8
- package/fixtures/valid/projection-manifest.json +47 -169
- package/fixtures/valid/projection-result.json +5 -5
- package/fixtures/valid/refactor-decision.json +38 -0
- package/fixtures/valid/runtime-recommendation-show.json +1 -0
- package/fixtures/valid/runtime-refactor-scan.json +6 -0
- package/package.json +1 -1
- package/schemas/repo/refactor-decision.schema.json +218 -0
- package/schemas/runtime/archctx-capabilities.schema.json +2 -2
- package/schemas/runtime/architecture-debt.schema.json +222 -0
- package/schemas/runtime/capability-dependencies.schema.json +10 -6
- package/schemas/runtime/changeset.schema.json +24 -1
- package/schemas/runtime/docs-anchors.schema.json +4 -3
- package/schemas/runtime/hook-debt-checkpoint.schema.json +69 -0
- package/schemas/runtime/native-context-export.schema.json +823 -0
- package/schemas/runtime/native-model-issue.schema.json +18 -0
- package/schemas/runtime/practice-checkpoint.schema.json +1 -0
- package/schemas/runtime/product-version-manifest.schema.json +2 -2
- package/schemas/runtime/projection-apply-readback.schema.json +470 -7
- package/schemas/runtime/projection-apply-recovery.schema.json +2 -2
- package/schemas/runtime/projection-manifest.schema.json +81 -186
- package/schemas/runtime/projection-result.schema.json +132 -5
- package/schemas/runtime/projection-target.schema.json +4 -1
- package/schemas/runtime/recommendation-v3.schema.json +106 -0
- package/schemas/runtime/review-result.schema.json +1 -0
- package/schemas/runtime/runtime-recommendation-show.schema.json +234 -13
- package/schemas/runtime/runtime-refactor-scan.schema.json +171 -0
- package/schemas/runtime/structural-observation-payload.schema.json +106 -0
- package/src/architecture-debt.ts +115 -0
- package/src/changeset-recovery.ts +49 -0
- package/src/docs-anchors.ts +2 -1
- package/src/failure.ts +810 -0
- package/src/index.ts +6 -0
- package/src/ledger.ts +14 -3
- package/src/native-context.ts +279 -0
- package/src/ownership-dependencies.ts +39 -1
- package/src/ports.ts +4 -0
- package/src/practices.ts +2 -0
- package/src/product-version.ts +37 -4
- package/src/projection-manifest.ts +372 -130
- package/src/projection-recovery.ts +111 -0
- package/src/projection.ts +443 -11
- package/src/refactor.ts +525 -2
- package/src/schema.ts +8 -0
- package/src/validator.ts +8 -3
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"repository",
|
|
10
10
|
"worktree",
|
|
11
11
|
"requestId",
|
|
12
|
+
"namespace",
|
|
12
13
|
"request",
|
|
13
14
|
"trackedFileCount",
|
|
14
15
|
"snapshot",
|
|
@@ -35,6 +36,11 @@
|
|
|
35
36
|
"type": "string",
|
|
36
37
|
"pattern": "^refactor_request\\.[a-f0-9]{16}$"
|
|
37
38
|
},
|
|
39
|
+
"namespace": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"pattern": "^[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$",
|
|
42
|
+
"description": "The manifest product.id every refactor subject selector of this scan is scoped by (ADR-0046)."
|
|
43
|
+
},
|
|
38
44
|
"request": {
|
|
39
45
|
"$ref": "#/$defs/refactorRequest"
|
|
40
46
|
},
|
|
@@ -389,6 +395,7 @@
|
|
|
389
395
|
"required": [
|
|
390
396
|
"kind",
|
|
391
397
|
"memberNodeIds",
|
|
398
|
+
"directions",
|
|
392
399
|
"edges",
|
|
393
400
|
"totalCount",
|
|
394
401
|
"truncated"
|
|
@@ -397,6 +404,80 @@
|
|
|
397
404
|
"kind": {
|
|
398
405
|
"const": "cycle"
|
|
399
406
|
},
|
|
407
|
+
"memberNodeIds": {
|
|
408
|
+
"type": "array",
|
|
409
|
+
"minItems": 2,
|
|
410
|
+
"maxItems": 2,
|
|
411
|
+
"uniqueItems": true,
|
|
412
|
+
"items": {
|
|
413
|
+
"type": "string",
|
|
414
|
+
"minLength": 1
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
"directions": {
|
|
418
|
+
"type": "array",
|
|
419
|
+
"minItems": 2,
|
|
420
|
+
"maxItems": 2,
|
|
421
|
+
"description": "[thin, thick] direction of the pair, each with its import-edge population and sampled count.",
|
|
422
|
+
"items": {
|
|
423
|
+
"type": "object",
|
|
424
|
+
"additionalProperties": false,
|
|
425
|
+
"required": [
|
|
426
|
+
"fromNodeId",
|
|
427
|
+
"toNodeId",
|
|
428
|
+
"totalCount",
|
|
429
|
+
"sampledCount"
|
|
430
|
+
],
|
|
431
|
+
"properties": {
|
|
432
|
+
"fromNodeId": {
|
|
433
|
+
"type": "string",
|
|
434
|
+
"minLength": 1
|
|
435
|
+
},
|
|
436
|
+
"toNodeId": {
|
|
437
|
+
"type": "string",
|
|
438
|
+
"minLength": 1
|
|
439
|
+
},
|
|
440
|
+
"totalCount": {
|
|
441
|
+
"type": "integer",
|
|
442
|
+
"minimum": 1
|
|
443
|
+
},
|
|
444
|
+
"sampledCount": {
|
|
445
|
+
"type": "integer",
|
|
446
|
+
"minimum": 0
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
"edges": {
|
|
452
|
+
"type": "array",
|
|
453
|
+
"maxItems": 20,
|
|
454
|
+
"items": {
|
|
455
|
+
"$ref": "#/$defs/evidenceImportEdge"
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
"totalCount": {
|
|
459
|
+
"type": "integer",
|
|
460
|
+
"minimum": 0
|
|
461
|
+
},
|
|
462
|
+
"truncated": {
|
|
463
|
+
"type": "boolean"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"type": "object",
|
|
469
|
+
"additionalProperties": false,
|
|
470
|
+
"required": [
|
|
471
|
+
"kind",
|
|
472
|
+
"memberNodeIds",
|
|
473
|
+
"edges",
|
|
474
|
+
"totalCount",
|
|
475
|
+
"truncated"
|
|
476
|
+
],
|
|
477
|
+
"properties": {
|
|
478
|
+
"kind": {
|
|
479
|
+
"const": "residual-cycle"
|
|
480
|
+
},
|
|
400
481
|
"memberNodeIds": {
|
|
401
482
|
"$ref": "#/$defs/evidenceIdList"
|
|
402
483
|
},
|
|
@@ -576,6 +657,16 @@
|
|
|
576
657
|
"null"
|
|
577
658
|
]
|
|
578
659
|
},
|
|
660
|
+
"memberNodeIds": {
|
|
661
|
+
"type": "array",
|
|
662
|
+
"minItems": 2,
|
|
663
|
+
"uniqueItems": true,
|
|
664
|
+
"items": {
|
|
665
|
+
"type": "string",
|
|
666
|
+
"minLength": 1
|
|
667
|
+
},
|
|
668
|
+
"description": "Present exactly for moduleDependencies.* metrics: the pair ends or the residual cycle members, sorted."
|
|
669
|
+
},
|
|
579
670
|
"operator": {
|
|
580
671
|
"enum": [
|
|
581
672
|
"absent",
|
|
@@ -867,6 +958,7 @@
|
|
|
867
958
|
"direction-violation",
|
|
868
959
|
"evidence-gap",
|
|
869
960
|
"ownership-ambiguous",
|
|
961
|
+
"residual-cycle",
|
|
870
962
|
"undeclared-footprint",
|
|
871
963
|
"unowned-paths"
|
|
872
964
|
]
|
|
@@ -981,6 +1073,26 @@
|
|
|
981
1073
|
}
|
|
982
1074
|
}
|
|
983
1075
|
},
|
|
1076
|
+
{
|
|
1077
|
+
"if": {
|
|
1078
|
+
"properties": {
|
|
1079
|
+
"kind": {
|
|
1080
|
+
"const": "residual-cycle"
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
"then": {
|
|
1085
|
+
"properties": {
|
|
1086
|
+
"evidence": {
|
|
1087
|
+
"properties": {
|
|
1088
|
+
"kind": {
|
|
1089
|
+
"const": "residual-cycle"
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
984
1096
|
{
|
|
985
1097
|
"if": {
|
|
986
1098
|
"properties": {
|
|
@@ -1719,6 +1831,7 @@
|
|
|
1719
1831
|
"modelDigest",
|
|
1720
1832
|
"codeFacts",
|
|
1721
1833
|
"modules",
|
|
1834
|
+
"moduleDependencies",
|
|
1722
1835
|
"repositorySummary",
|
|
1723
1836
|
"createdAt",
|
|
1724
1837
|
"snapshotDigest"
|
|
@@ -1820,6 +1933,33 @@
|
|
|
1820
1933
|
"$ref": "#/$defs/moduleStatistics"
|
|
1821
1934
|
}
|
|
1822
1935
|
},
|
|
1936
|
+
"moduleDependencies": {
|
|
1937
|
+
"type": "array",
|
|
1938
|
+
"description": "Ordered cross-module dependency table, sorted by (fromNodeId, toNodeId).",
|
|
1939
|
+
"items": {
|
|
1940
|
+
"type": "object",
|
|
1941
|
+
"additionalProperties": false,
|
|
1942
|
+
"required": [
|
|
1943
|
+
"fromNodeId",
|
|
1944
|
+
"toNodeId",
|
|
1945
|
+
"distinctFileEdgeCount"
|
|
1946
|
+
],
|
|
1947
|
+
"properties": {
|
|
1948
|
+
"fromNodeId": {
|
|
1949
|
+
"type": "string",
|
|
1950
|
+
"minLength": 1
|
|
1951
|
+
},
|
|
1952
|
+
"toNodeId": {
|
|
1953
|
+
"type": "string",
|
|
1954
|
+
"minLength": 1
|
|
1955
|
+
},
|
|
1956
|
+
"distinctFileEdgeCount": {
|
|
1957
|
+
"type": "integer",
|
|
1958
|
+
"minimum": 1
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
},
|
|
1823
1963
|
"repositorySummary": {
|
|
1824
1964
|
"type": "object",
|
|
1825
1965
|
"additionalProperties": false,
|
|
@@ -2095,6 +2235,7 @@
|
|
|
2095
2235
|
"required": [
|
|
2096
2236
|
"kind",
|
|
2097
2237
|
"subjectSelectorId",
|
|
2238
|
+
"affectedNodeIds",
|
|
2098
2239
|
"signalIds",
|
|
2099
2240
|
"metrics",
|
|
2100
2241
|
"evidence"
|
|
@@ -2106,6 +2247,7 @@
|
|
|
2106
2247
|
"direction-violation",
|
|
2107
2248
|
"evidence-gap",
|
|
2108
2249
|
"ownership-ambiguous",
|
|
2250
|
+
"residual-cycle",
|
|
2109
2251
|
"undeclared-footprint",
|
|
2110
2252
|
"unowned-paths"
|
|
2111
2253
|
]
|
|
@@ -2115,6 +2257,15 @@
|
|
|
2115
2257
|
"minLength": 1,
|
|
2116
2258
|
"pattern": "\\S"
|
|
2117
2259
|
},
|
|
2260
|
+
"affectedNodeIds": {
|
|
2261
|
+
"type": "array",
|
|
2262
|
+
"uniqueItems": true,
|
|
2263
|
+
"items": {
|
|
2264
|
+
"type": "string",
|
|
2265
|
+
"minLength": 1
|
|
2266
|
+
},
|
|
2267
|
+
"description": "Declared nodes the observation is about, sorted and unique; [] for a repository-wide fact."
|
|
2268
|
+
},
|
|
2118
2269
|
"signalIds": {
|
|
2119
2270
|
"type": "array",
|
|
2120
2271
|
"uniqueItems": true,
|
|
@@ -2210,6 +2361,26 @@
|
|
|
2210
2361
|
}
|
|
2211
2362
|
}
|
|
2212
2363
|
},
|
|
2364
|
+
{
|
|
2365
|
+
"if": {
|
|
2366
|
+
"properties": {
|
|
2367
|
+
"kind": {
|
|
2368
|
+
"const": "residual-cycle"
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
},
|
|
2372
|
+
"then": {
|
|
2373
|
+
"properties": {
|
|
2374
|
+
"evidence": {
|
|
2375
|
+
"properties": {
|
|
2376
|
+
"kind": {
|
|
2377
|
+
"const": "residual-cycle"
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
},
|
|
2213
2384
|
{
|
|
2214
2385
|
"if": {
|
|
2215
2386
|
"properties": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"direction-violation",
|
|
25
25
|
"evidence-gap",
|
|
26
26
|
"ownership-ambiguous",
|
|
27
|
+
"residual-cycle",
|
|
27
28
|
"undeclared-footprint",
|
|
28
29
|
"unowned-paths"
|
|
29
30
|
]
|
|
@@ -138,6 +139,26 @@
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
},
|
|
142
|
+
{
|
|
143
|
+
"if": {
|
|
144
|
+
"properties": {
|
|
145
|
+
"kind": {
|
|
146
|
+
"const": "residual-cycle"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"then": {
|
|
151
|
+
"properties": {
|
|
152
|
+
"evidence": {
|
|
153
|
+
"properties": {
|
|
154
|
+
"kind": {
|
|
155
|
+
"const": "residual-cycle"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
141
162
|
{
|
|
142
163
|
"if": {
|
|
143
164
|
"properties": {
|
|
@@ -344,6 +365,7 @@
|
|
|
344
365
|
"required": [
|
|
345
366
|
"kind",
|
|
346
367
|
"memberNodeIds",
|
|
368
|
+
"directions",
|
|
347
369
|
"edges",
|
|
348
370
|
"totalCount",
|
|
349
371
|
"truncated"
|
|
@@ -352,6 +374,80 @@
|
|
|
352
374
|
"kind": {
|
|
353
375
|
"const": "cycle"
|
|
354
376
|
},
|
|
377
|
+
"memberNodeIds": {
|
|
378
|
+
"type": "array",
|
|
379
|
+
"minItems": 2,
|
|
380
|
+
"maxItems": 2,
|
|
381
|
+
"uniqueItems": true,
|
|
382
|
+
"items": {
|
|
383
|
+
"type": "string",
|
|
384
|
+
"minLength": 1
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"directions": {
|
|
388
|
+
"type": "array",
|
|
389
|
+
"minItems": 2,
|
|
390
|
+
"maxItems": 2,
|
|
391
|
+
"description": "[thin, thick] direction of the pair, each with its import-edge population and sampled count.",
|
|
392
|
+
"items": {
|
|
393
|
+
"type": "object",
|
|
394
|
+
"additionalProperties": false,
|
|
395
|
+
"required": [
|
|
396
|
+
"fromNodeId",
|
|
397
|
+
"toNodeId",
|
|
398
|
+
"totalCount",
|
|
399
|
+
"sampledCount"
|
|
400
|
+
],
|
|
401
|
+
"properties": {
|
|
402
|
+
"fromNodeId": {
|
|
403
|
+
"type": "string",
|
|
404
|
+
"minLength": 1
|
|
405
|
+
},
|
|
406
|
+
"toNodeId": {
|
|
407
|
+
"type": "string",
|
|
408
|
+
"minLength": 1
|
|
409
|
+
},
|
|
410
|
+
"totalCount": {
|
|
411
|
+
"type": "integer",
|
|
412
|
+
"minimum": 1
|
|
413
|
+
},
|
|
414
|
+
"sampledCount": {
|
|
415
|
+
"type": "integer",
|
|
416
|
+
"minimum": 0
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
"edges": {
|
|
422
|
+
"type": "array",
|
|
423
|
+
"maxItems": 20,
|
|
424
|
+
"items": {
|
|
425
|
+
"$ref": "#/$defs/evidenceImportEdge"
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
"totalCount": {
|
|
429
|
+
"type": "integer",
|
|
430
|
+
"minimum": 0
|
|
431
|
+
},
|
|
432
|
+
"truncated": {
|
|
433
|
+
"type": "boolean"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"type": "object",
|
|
439
|
+
"additionalProperties": false,
|
|
440
|
+
"required": [
|
|
441
|
+
"kind",
|
|
442
|
+
"memberNodeIds",
|
|
443
|
+
"edges",
|
|
444
|
+
"totalCount",
|
|
445
|
+
"truncated"
|
|
446
|
+
],
|
|
447
|
+
"properties": {
|
|
448
|
+
"kind": {
|
|
449
|
+
"const": "residual-cycle"
|
|
450
|
+
},
|
|
355
451
|
"memberNodeIds": {
|
|
356
452
|
"$ref": "#/$defs/evidenceIdList"
|
|
357
453
|
},
|
|
@@ -531,6 +627,16 @@
|
|
|
531
627
|
"null"
|
|
532
628
|
]
|
|
533
629
|
},
|
|
630
|
+
"memberNodeIds": {
|
|
631
|
+
"type": "array",
|
|
632
|
+
"minItems": 2,
|
|
633
|
+
"uniqueItems": true,
|
|
634
|
+
"items": {
|
|
635
|
+
"type": "string",
|
|
636
|
+
"minLength": 1
|
|
637
|
+
},
|
|
638
|
+
"description": "Present exactly for moduleDependencies.* metrics: the pair ends or the residual cycle members, sorted."
|
|
639
|
+
},
|
|
534
640
|
"operator": {
|
|
535
641
|
"enum": [
|
|
536
642
|
"absent",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { digestJson, type ArchContextError, type Json } from "./schema";
|
|
2
|
+
|
|
3
|
+
/** Measured architecture work, distinct from generated-file freshness and source byte stamps. */
|
|
4
|
+
export const ARCHITECTURE_DEBT_SCHEMA_VERSION = "archcontext.architecture-debt/v1" as const;
|
|
5
|
+
export const ARCHITECTURE_DEBT_KINDS = ["unowned-path", "ambiguous-owner", "undeclared-dependency", "flow-proof-failed", "stale-anchor", "practice-regression"] as const;
|
|
6
|
+
export type ArchitectureDebtKind = typeof ARCHITECTURE_DEBT_KINDS[number];
|
|
7
|
+
export const ARCHITECTURE_DEBT_CHECKS = ["ownership", "anchors", "dependencies", "flows", "practices"] as const;
|
|
8
|
+
export type ArchitectureDebtCheck = typeof ARCHITECTURE_DEBT_CHECKS[number];
|
|
9
|
+
|
|
10
|
+
export interface ArchitectureDebtItemV1 {
|
|
11
|
+
/** Content identity, independent of absolute root, branch, timestamps and source byte stamps. */
|
|
12
|
+
id: string;
|
|
13
|
+
kind: ArchitectureDebtKind;
|
|
14
|
+
/** Canonical producer identities. Empty for a source path with no owner. */
|
|
15
|
+
nodeIds: string[];
|
|
16
|
+
capabilityIds: string[];
|
|
17
|
+
paths: string[];
|
|
18
|
+
reasonCode: string;
|
|
19
|
+
action: string;
|
|
20
|
+
/** Bounded metadata only; never source bodies or arbitrary provider output. */
|
|
21
|
+
details: Record<string, string | number | string[]>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ArchitectureDebtCheckV1 {
|
|
25
|
+
check: ArchitectureDebtCheck;
|
|
26
|
+
coverage: "complete" | "partial" | "unknown";
|
|
27
|
+
reasonCodes: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ArchitectureDebtReportV1 {
|
|
31
|
+
schemaVersion: typeof ARCHITECTURE_DEBT_SCHEMA_VERSION;
|
|
32
|
+
scope: { kind: "repository" | "changed-paths"; changedPaths: string[]; capabilityIds: string[] };
|
|
33
|
+
/** A partial/unknown report with no items does not prove absence of debt. */
|
|
34
|
+
coverage: "complete" | "partial" | "unknown";
|
|
35
|
+
coverageReasonCodes: string[];
|
|
36
|
+
checks: ArchitectureDebtCheckV1[];
|
|
37
|
+
items: ArchitectureDebtItemV1[];
|
|
38
|
+
actionsRequired: string[];
|
|
39
|
+
/** Binds the entire debt report. Checkpoint's existing resultDigest binds practice guidance only. */
|
|
40
|
+
digest: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ArchitectureDebtHookLogV1 {
|
|
44
|
+
schemaVersion: "archcontext.hook-log/v1";
|
|
45
|
+
event: string;
|
|
46
|
+
elapsedMs: number;
|
|
47
|
+
pathCount: number;
|
|
48
|
+
changedPathDigest: string;
|
|
49
|
+
reasonCode: string;
|
|
50
|
+
failOpen: boolean;
|
|
51
|
+
egress: "none";
|
|
52
|
+
network: "forbidden";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Hook-only observation; it never advances the practice baseline. */
|
|
56
|
+
export interface ArchitectureDebtHookCheckpointV1 {
|
|
57
|
+
schemaVersion: "archcontext.hook-debt-checkpoint/v1";
|
|
58
|
+
debt: ArchitectureDebtReportV1;
|
|
59
|
+
hookLog: ArchitectureDebtHookLogV1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Unavailable observation, never a clean debt report. Invalid arguments remain errors. */
|
|
63
|
+
export interface ArchitectureDebtHookFailureV1 {
|
|
64
|
+
schemaVersion: "archcontext.hook-checkpoint-fail-open/v1";
|
|
65
|
+
accepted: false;
|
|
66
|
+
failOpen: true;
|
|
67
|
+
coverage: "unknown";
|
|
68
|
+
reasonCode: string;
|
|
69
|
+
error: ArchContextError;
|
|
70
|
+
event: string;
|
|
71
|
+
pathCount: number;
|
|
72
|
+
egress: "none";
|
|
73
|
+
network: "forbidden";
|
|
74
|
+
hookLog: ArchitectureDebtHookLogV1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function architectureDebtReportIssues(report: ArchitectureDebtReportV1): string[] {
|
|
78
|
+
const issues: string[] = [];
|
|
79
|
+
const sorted = (name: string, values: string[]) => {
|
|
80
|
+
if (values.some((v, i) => i > 0 && v <= values[i - 1]!)) issues.push(`${name} must be sorted and unique`);
|
|
81
|
+
};
|
|
82
|
+
if (report.schemaVersion !== ARCHITECTURE_DEBT_SCHEMA_VERSION) issues.push("unsupported schemaVersion");
|
|
83
|
+
const { digest, ...body } = report;
|
|
84
|
+
if (digest !== digestJson(body as unknown as Json)) issues.push("digest must bind the debt report");
|
|
85
|
+
sorted("items", report.items.map(item => item.id));
|
|
86
|
+
sorted("actionsRequired", report.actionsRequired);
|
|
87
|
+
sorted("coverageReasonCodes", report.coverageReasonCodes);
|
|
88
|
+
sorted("scope.changedPaths", report.scope.changedPaths);
|
|
89
|
+
sorted("scope.capabilityIds", report.scope.capabilityIds);
|
|
90
|
+
if (report.checks.length !== ARCHITECTURE_DEBT_CHECKS.length || ARCHITECTURE_DEBT_CHECKS.some((check, i) => report.checks[i]?.check !== check)) issues.push("checks must enumerate every producer in contract order");
|
|
91
|
+
const coverage = report.checks.every(check => check.coverage === "complete") ? "complete"
|
|
92
|
+
: report.checks.every(check => check.coverage === "unknown") ? "unknown" : "partial";
|
|
93
|
+
if (report.coverage !== coverage) issues.push("coverage must describe all checks");
|
|
94
|
+
const reasons = [...new Set(report.checks.flatMap(check => check.reasonCodes.map(reason => `${check.check}:${reason}`)))].sort();
|
|
95
|
+
if (JSON.stringify(reasons) !== JSON.stringify(report.coverageReasonCodes)) issues.push("coverageReasonCodes must describe check gaps");
|
|
96
|
+
for (const check of report.checks) {
|
|
97
|
+
sorted(`${check.check}.reasonCodes`, check.reasonCodes);
|
|
98
|
+
if ((check.coverage === "complete") !== (check.reasonCodes.length === 0)) issues.push(`${check.check} must name every coverage gap`);
|
|
99
|
+
}
|
|
100
|
+
if (report.coverage === "unknown" && report.items.length > 0) issues.push("unknown coverage cannot contain measured items");
|
|
101
|
+
const actions = [...new Set(report.items.map(item => item.action))].sort();
|
|
102
|
+
if (JSON.stringify(actions) !== JSON.stringify(report.actionsRequired)) issues.push("actionsRequired must name the measured debt actions");
|
|
103
|
+
for (const item of report.items) {
|
|
104
|
+
if (!ARCHITECTURE_DEBT_KINDS.includes(item.kind)) issues.push("unsupported debt kind");
|
|
105
|
+
if (!item.action || !item.reasonCode) issues.push("every item needs a reason and action");
|
|
106
|
+
sorted(`${item.id}.nodeIds`, item.nodeIds);
|
|
107
|
+
sorted(`${item.id}.capabilityIds`, item.capabilityIds);
|
|
108
|
+
sorted(`${item.id}.paths`, item.paths);
|
|
109
|
+
const strings = [item.action, item.reasonCode, ...item.nodeIds, ...item.capabilityIds, ...item.paths,
|
|
110
|
+
...Object.values(item.details).flatMap(value => typeof value === "string" ? [value] : Array.isArray(value) ? value : [])];
|
|
111
|
+
if (strings.some(value => value.length === 0 || value.length > 1024)) issues.push(`${item.id} contains invalid metadata strings`);
|
|
112
|
+
if (new TextEncoder().encode(JSON.stringify(item.details)).byteLength > 4096) issues.push(`${item.id}.details exceeds metadata budget`);
|
|
113
|
+
}
|
|
114
|
+
return issues;
|
|
115
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { digestJson, type Json } from "./schema";
|
|
2
|
+
export const CHANGESET_RECOVERY_DETAILS_LIMIT_BYTES = 262144;
|
|
3
|
+
export const CHANGESET_RECOVERY_READBACK = "runtimeStatus.changeSetRecovery.unresolvedJournals";
|
|
4
|
+
function own(value: unknown, key: string): unknown {
|
|
5
|
+
if (!value || typeof value !== "object") throw new Error("Recovery metadata must be own data");
|
|
6
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
7
|
+
if (!descriptor || !("value" in descriptor)) throw new Error("Recovery metadata cannot contain accessors");
|
|
8
|
+
return descriptor.value;
|
|
9
|
+
}
|
|
10
|
+
function record(value: unknown, fields: string[]): void {
|
|
11
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype,null].includes(Object.getPrototypeOf(value))) throw new Error("Recovery metadata must be a plain record");
|
|
12
|
+
const keys = Reflect.ownKeys(value);
|
|
13
|
+
if (keys.length !== fields.length || keys.some(key=>typeof key !== "string" || !fields.includes(key))) throw new Error("Recovery metadata has unknown fields");
|
|
14
|
+
}
|
|
15
|
+
function journalsSnapshot(value: unknown): Json[] {
|
|
16
|
+
if (!Array.isArray(value)) throw new Error("Recovery journals must be an array");
|
|
17
|
+
const length = own(value,"length");
|
|
18
|
+
if (!Number.isSafeInteger(length) || (length as number) < 0 || Reflect.ownKeys(value).length !== (length as number)+1) throw new Error("Recovery journals must be dense own data");
|
|
19
|
+
return Array.from({length: length as number},(_,index)=>{
|
|
20
|
+
const journal=own(value,String(index));
|
|
21
|
+
const fields=["journalId","changeSetId","root","reason"];
|
|
22
|
+
record(journal,fields);
|
|
23
|
+
return Object.fromEntries(fields.map(key=>{
|
|
24
|
+
const item=own(journal,key);
|
|
25
|
+
if(typeof item!=="string") throw new Error("Recovery authority must be strings");
|
|
26
|
+
return [key,item];
|
|
27
|
+
}));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Complete authority is copied whole. Oversized authority remains available through existing status/health. */
|
|
31
|
+
export function changeSetRecoveryDetails(journals: unknown): {[key:string]:Json} {
|
|
32
|
+
const unresolvedJournals=journalsSnapshot(journals);
|
|
33
|
+
const full={unresolvedJournals};
|
|
34
|
+
if(new TextEncoder().encode(JSON.stringify(full)).length<=CHANGESET_RECOVERY_DETAILS_LIMIT_BYTES) return full;
|
|
35
|
+
return {manualRecoveryRequired:true, authorityReadbackRequired:true, authorityReadback:CHANGESET_RECOVERY_READBACK, journalCount:unresolvedJournals.length, authorityDigest:digestJson(unresolvedJournals)};
|
|
36
|
+
}
|
|
37
|
+
export function changeSetRecoveryDetailsSnapshot(value:unknown): {[key:string]:Json} {
|
|
38
|
+
if(value && typeof value==="object" && Object.hasOwn(value,"unresolvedJournals")) {
|
|
39
|
+
record(value,["unresolvedJournals"]);
|
|
40
|
+
const full={unresolvedJournals:journalsSnapshot(own(value,"unresolvedJournals"))};
|
|
41
|
+
if(new TextEncoder().encode(JSON.stringify(full)).length>CHANGESET_RECOVERY_DETAILS_LIMIT_BYTES) throw new Error("Recovery authority exceeds budget");
|
|
42
|
+
return full;
|
|
43
|
+
}
|
|
44
|
+
const fields=["manualRecoveryRequired","authorityReadbackRequired","authorityReadback","journalCount","authorityDigest"];
|
|
45
|
+
record(value,fields);
|
|
46
|
+
const result=Object.fromEntries(fields.map(key=>[key,own(value,key)]));
|
|
47
|
+
if(result.manualRecoveryRequired!==true || result.authorityReadbackRequired!==true || result.authorityReadback!==CHANGESET_RECOVERY_READBACK || !Number.isSafeInteger(result.journalCount) || (result.journalCount as number)<0 || typeof result.authorityDigest!=="string" || !/^sha256:[a-f0-9]{64}$/.test(result.authorityDigest)) throw new Error("Invalid manual recovery authority reference");
|
|
48
|
+
return result as {[key:string]:Json};
|
|
49
|
+
}
|
package/src/docs-anchors.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const DOCS_ANCHORS_REPORT_SCHEMA_VERSION = "archcontext.docs-anchors/v1" as const;
|
|
8
8
|
|
|
9
|
-
export const DOCS_ANCHOR_FINDING_KINDS = ["line-moved", "line-out-of-range", "missing-path", "missing-symbol"] as const;
|
|
9
|
+
export const DOCS_ANCHOR_FINDING_KINDS = ["line-moved", "line-out-of-range", "missing-path", "missing-symbol", "unknown-selector"] as const;
|
|
10
10
|
export type DocsAnchorFindingKind = (typeof DOCS_ANCHOR_FINDING_KINDS)[number];
|
|
11
11
|
|
|
12
12
|
/** Why a citation was found but one of its checks could not run. */
|
|
@@ -111,6 +111,7 @@ function docsAnchorFindingIssues(finding: DocsAnchorFindingV1, prefix: string):
|
|
|
111
111
|
const { citation } = finding;
|
|
112
112
|
if (finding.kind === "missing-path" && citation.path === undefined) issues.push(`${prefix} missing-path must name citation.path`);
|
|
113
113
|
if (finding.kind === "missing-symbol" && citation.symbol === undefined) issues.push(`${prefix} missing-symbol must name citation.symbol`);
|
|
114
|
+
if (finding.kind === "unknown-selector" && citation.symbol === undefined) issues.push(`${prefix} unknown-selector must name citation.symbol`);
|
|
114
115
|
if (finding.kind === "line-out-of-range" && (citation.line === undefined || finding.lineCount === undefined)) {
|
|
115
116
|
issues.push(`${prefix} line-out-of-range must name citation.line and lineCount`);
|
|
116
117
|
}
|