agent-doc-system 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/INSTALL_FOR_AGENTS.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +151 -2
  4. package/adapters/cache.mjs +27 -0
  5. package/adapters/contract-consumers.mjs +56 -0
  6. package/adapters/database.mjs +116 -0
  7. package/adapters/deploy-config.mjs +34 -0
  8. package/adapters/dockerfile.mjs +26 -0
  9. package/adapters/events.mjs +145 -0
  10. package/adapters/generic.mjs +86 -0
  11. package/adapters/github-actions.mjs +106 -0
  12. package/adapters/go.mjs +91 -0
  13. package/adapters/graphql.mjs +28 -0
  14. package/adapters/index.mjs +55 -0
  15. package/adapters/node.mjs +118 -0
  16. package/adapters/object-store.mjs +64 -0
  17. package/adapters/oidc.mjs +56 -0
  18. package/adapters/openapi.mjs +50 -0
  19. package/adapters/pnpm.mjs +42 -0
  20. package/adapters/protobuf.mjs +35 -0
  21. package/adapters/registry.mjs +27 -0
  22. package/adapters/resource-client.mjs +61 -0
  23. package/adapters/turborepo.mjs +135 -0
  24. package/adapters/typescript.mjs +85 -0
  25. package/adapters/wrangler.mjs +206 -0
  26. package/bin/agentdoc.mjs +478 -0
  27. package/bin/usage.txt +26 -0
  28. package/core/acceptances.mjs +69 -0
  29. package/core/audit.mjs +407 -0
  30. package/core/authority/classes.mjs +39 -0
  31. package/core/authority/engine.mjs +243 -0
  32. package/core/authority/facts.mjs +41 -0
  33. package/core/codes.mjs +119 -0
  34. package/core/compile.mjs +881 -0
  35. package/core/contracts.mjs +121 -0
  36. package/core/derive.mjs +102 -0
  37. package/core/descriptors.mjs +486 -0
  38. package/core/determinism.mjs +95 -0
  39. package/core/discover.mjs +147 -0
  40. package/core/facts.mjs +53 -0
  41. package/core/fsx.mjs +342 -0
  42. package/core/graph.mjs +260 -0
  43. package/core/impact.mjs +175 -0
  44. package/core/indexes.mjs +165 -0
  45. package/core/jsonschema.mjs +298 -0
  46. package/core/observations.mjs +129 -0
  47. package/core/provenance.mjs +83 -0
  48. package/core/query.mjs +384 -0
  49. package/core/relations.mjs +286 -0
  50. package/core/scaffold.mjs +322 -0
  51. package/core/secrets.mjs +92 -0
  52. package/core/sourcescan.mjs +88 -0
  53. package/core/toml.mjs +163 -0
  54. package/core/yaml.mjs +475 -0
  55. package/docs/ADAPTERS.md +172 -0
  56. package/docs/AUTHORITY.md +260 -0
  57. package/docs/CI.md +136 -0
  58. package/docs/COMPARISON-KODA.md +164 -0
  59. package/docs/EVALS.md +166 -0
  60. package/docs/MIGRATION.md +193 -0
  61. package/docs/PROVENANCE.md +133 -0
  62. package/docs/QUERY.md +180 -0
  63. package/docs/SPEC.md +339 -0
  64. package/docs/VERIFICATION.md +28 -0
  65. package/docs/ci/generic-ci.sh +28 -0
  66. package/docs/ci/github-actions.yml +35 -0
  67. package/evals/harness.mjs +426 -0
  68. package/evals/scenarios/fixture-a-01-change-api.json +9 -0
  69. package/evals/scenarios/fixture-a-02-change-db.json +9 -0
  70. package/evals/scenarios/fixture-a-03-enforce-constraint.json +9 -0
  71. package/evals/scenarios/fixture-b-01-change-proto.json +9 -0
  72. package/evals/scenarios/fixture-b-02-change-write-path.json +9 -0
  73. package/evals/scenarios/fixture-b-03-change-shared-lib.json +9 -0
  74. package/evals/scenarios/fixture-b-04-journey.json +9 -0
  75. package/evals/scenarios/fixture-c-01-mixed-runtime-edge.json +19 -0
  76. package/evals/scenarios/fixture-c-02-stale-doc-contradiction.json +9 -0
  77. package/evals/scenarios/fixture-c-03-shared-vocabulary.json +9 -0
  78. package/evals/scenarios/fixture-c-04-contract-change.json +9 -0
  79. package/evals/scenarios/koda-01-modify-api-consumer.json +26 -0
  80. package/evals/scenarios/koda-02-change-db-schema.json +20 -0
  81. package/evals/scenarios/koda-03-change-auth.json +21 -0
  82. package/evals/scenarios/koda-04-add-event-producer.json +19 -0
  83. package/evals/scenarios/koda-05-modify-worker-binding.json +25 -0
  84. package/evals/scenarios/koda-06-change-cross-component-endpoint.json +22 -0
  85. package/evals/scenarios/koda-07-change-shared-package.json +16 -0
  86. package/evals/scenarios/koda-08-change-runtime-cron.json +23 -0
  87. package/evals/scenarios/koda-09-debug-production-mismatch.json +26 -0
  88. package/evals/thresholds.json +11 -0
  89. package/package.json +46 -4
  90. package/schemas/api.schema.json +84 -0
  91. package/schemas/common.schema.json +107 -0
  92. package/schemas/component.schema.json +40 -0
  93. package/schemas/config.schema.json +685 -0
  94. package/schemas/domain.schema.json +14 -0
  95. package/schemas/graph.schema.json +1084 -0
  96. package/schemas/journeys.schema.json +47 -0
  97. package/schemas/observation.schema.json +143 -0
  98. package/schemas/resource.schema.json +28 -0
  99. package/schemas/system.schema.json +26 -0
  100. package/skills/agent-doc-system/SKILL.md +113 -0
  101. package/skills/agent-doc-system/references/authority-and-conflicts.md +70 -0
  102. package/skills/agent-doc-system/references/cli.md +89 -0
  103. package/skills/agent-doc-system/references/create-migrate.md +101 -0
  104. package/skills/agent-doc-system/references/maintenance.md +52 -0
  105. package/templates/ADR.md +26 -0
  106. package/templates/ARCHITECTURE.md +19 -0
  107. package/templates/CONSTRAINTS.md +17 -0
  108. package/templates/PRODUCT.md +17 -0
  109. package/templates/api.yaml +10 -0
  110. package/templates/ci.yml +31 -0
  111. package/templates/component.yaml +14 -0
  112. package/templates/domain.yaml +6 -0
  113. package/templates/journey.yaml +7 -0
  114. package/templates/observation.yaml +19 -0
  115. package/templates/resource.yaml +8 -0
  116. package/templates/system.yaml +10 -0
  117. package/templates/warning-acceptance.yaml +14 -0
@@ -0,0 +1,243 @@
1
+ // Authority rules and conflict resolution.
2
+ //
3
+ // There is deliberately NO global precedence order. Authority depends on the
4
+ // fact: an OpenAPI file may be authoritative for the desired wire contract
5
+ // while a runtime observation is authoritative for what is deployed right now,
6
+ // and source code is authoritative for the behaviour shipped by a commit.
7
+ //
8
+ // A fact with an authority rule gets an election. A fact with NO matching rule
9
+ // and disagreeing evidence stays UNRESOLVED and raises a conflict. That is the
10
+ // fail-closed default: silence is not a resolution.
11
+ import { AgentDocError, CODES } from "../codes.mjs";
12
+ import { EVIDENCE_CLASSES, CONFIDENCE } from "./classes.mjs";
13
+
14
+ export class AuthorityEngine {
15
+ constructor(rules) {
16
+ this.rules = (rules || []).map((r) => {
17
+ let re;
18
+ try {
19
+ re = new RegExp(r.keyPattern, "u");
20
+ } catch {
21
+ throw new AgentDocError(CODES.CONFIG, "authority rule " + r.id + " has an invalid keyPattern: " + r.keyPattern);
22
+ }
23
+ for (const c of r.elect) {
24
+ if (!EVIDENCE_CLASSES.includes(c) || c === "UNRESOLVED") {
25
+ throw new AgentDocError(CODES.CONFIG, "authority rule " + r.id + " cannot elect " + c);
26
+ }
27
+ }
28
+ return { ...r, re };
29
+ });
30
+ for (let i = 0; i < this.rules.length; i++) {
31
+ for (let j = i + 1; j < this.rules.length; j++) {
32
+ if (this.rules[i].id === this.rules[j].id) {
33
+ throw new AgentDocError(CODES.CONFIG, "duplicate authority rule id " + this.rules[i].id);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ // Returns the most specific matching rule (longest pattern wins) or null.
39
+ ruleFor(subject, key) {
40
+ const target = subject + " " + key;
41
+ let best = null;
42
+ for (const r of this.rules) {
43
+ if (!r.re.test(target)) continue;
44
+ if (!best || r.keyPattern.length > best.keyPattern.length) best = r;
45
+ }
46
+ return best;
47
+ }
48
+ }
49
+
50
+ // Group assertions by (subject, key) and elect at most one per group.
51
+ //
52
+ // Election basis, in the order it is attempted — and each basis is only
53
+ // reachable when a rule explicitly allows it:
54
+ // reviewed-override an explicit human interpretation exists
55
+ // authority-rule the rule's elect list contains exactly one class present
56
+ // superset the elected value strictly contains the others
57
+ // recency the newest runtime observation, and only if the rule says so
58
+ // none fail closed
59
+ export function resolveAssertions(assertions, authority) {
60
+ const groups = new Map();
61
+ for (const a of assertions) {
62
+ // An assertion a later observation supersedes stays in the graph as
63
+ // history but does not compete here: it cannot win an election and cannot
64
+ // anchor a conflict.
65
+ if (a.status === "superseded") continue;
66
+ const gk = a.subject + "\u0000" + a.key;
67
+ if (!groups.has(gk)) groups.set(gk, []);
68
+ groups.get(gk).push(a);
69
+ }
70
+
71
+ const conflicts = [];
72
+ const elections = [];
73
+
74
+ for (const [gk, list] of [...groups.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
75
+ const [subject, key] = gk.split("\u0000");
76
+ const distinct = distinctValues(list);
77
+
78
+ if (distinct.length === 1) {
79
+ // Agreement across evidence classes is corroboration, not a conflict —
80
+ // but a lone heuristic candidate is not agreement with anything. It stays
81
+ // UNRESOLVED and never becomes an elected fact.
82
+ const allCandidate = list.every((a) => a.confidence === "candidate");
83
+ for (const a of list) a.status = allCandidate ? "candidate" : "elected";
84
+ const rule = authority.ruleFor(subject, key);
85
+ elections.push({ subject, key, ruleId: rule ? rule.id : "agreement", basis: "authority-rule", elected: list[0] });
86
+ if (allCandidate) {
87
+ conflicts.push({
88
+ subject,
89
+ key,
90
+ kind: "ambiguity",
91
+ status: "unresolved",
92
+ assertionIds: list.map((a) => a.id).sort(),
93
+ election: {
94
+ elected: null,
95
+ basis: "none",
96
+ ruleId: rule ? rule.id : "no-rule",
97
+ rationale: "only heuristic candidates exist for '" + key + "'; deterministic discovery cannot resolve it",
98
+ reviewWhen: rule ? rule.reviewWhen : null,
99
+ contradicted: [],
100
+ },
101
+ });
102
+ }
103
+ continue;
104
+ }
105
+
106
+ const rule = authority.ruleFor(subject, key);
107
+ const candidates = list.filter((a) => a.confidence !== "candidate");
108
+ const candidateOnly = list.length !== candidates.length;
109
+
110
+ // `reviewWhen` travels with the election so a consumer can report when this
111
+ // decision must be re-examined. A null here means "no rule governs it", which
112
+ // is a real state — and one that must never be rendered as if a condition
113
+ // existed.
114
+ const election = {
115
+ elected: null,
116
+ basis: "none",
117
+ ruleId: rule ? rule.id : "no-rule",
118
+ rationale: null,
119
+ reviewWhen: rule ? rule.reviewWhen : null,
120
+ contradicted: [],
121
+ };
122
+ let status = "unresolved";
123
+
124
+ // 1. reviewed override wins outright, but the divergence is still recorded.
125
+ const reviewed = candidates.filter((a) => a.evidenceClass === "REVIEWED_OVERRIDE");
126
+ if (rule && rule.elect.length === 1 && rule.elect[0] === "REVIEWED_OVERRIDE" && reviewed.length === 1) {
127
+ election.elected = reviewed[0];
128
+ election.basis = "reviewed-override";
129
+ election.rationale = rule.rationale;
130
+ status = "accepted";
131
+ }
132
+
133
+ // 2. authority rule with a single elected class present among candidates.
134
+ if (!election.elected && rule) {
135
+ const allowed = rule.elect.filter((c) => candidates.some((a) => a.evidenceClass === c));
136
+ if (allowed.length === 1) {
137
+ const winners = candidates.filter((a) => a.evidenceClass === allowed[0]);
138
+ if (winners.length === 1) {
139
+ election.elected = winners[0];
140
+ election.basis = "authority-rule";
141
+ election.rationale = rule.rationale;
142
+ status = "resolved";
143
+ } else if (winners.length > 1) {
144
+ election.rationale = "multiple same-class assertions disagree; no election";
145
+ }
146
+ } else if (allowed.length > 1) {
147
+ election.rationale = "authority rule " + rule.id + " elects several classes that are all present; no deterministic election";
148
+ }
149
+ }
150
+
151
+ // 3. superset: one candidate's value contains every other candidate's value.
152
+ if (!election.elected && candidates.length > 1) {
153
+ const sup = candidates.find((c) => candidates.every((o) => o === c || containsValue(c.value, o.value)));
154
+ if (sup) {
155
+ election.elected = sup;
156
+ election.basis = "superset";
157
+ election.rationale = "one observed value strictly contains the others";
158
+ status = "resolved";
159
+ }
160
+ }
161
+
162
+ // 4. recency: only when the rule names recency and the winner is a runtime fact.
163
+ if (!election.elected && rule && rule.elect.includes("OBSERVED_RUNTIME")) {
164
+ const runtime = candidates.filter((a) => a.evidenceClass === "OBSERVED_RUNTIME");
165
+ const dated = runtime.filter((a) => a.observed && a.observed.at);
166
+ if (dated.length > 1) {
167
+ const newest = dated.sort((a, b) => (a.observed.at < b.observed.at ? -1 : 1)).pop();
168
+ election.elected = newest;
169
+ election.basis = "recency";
170
+ election.rationale = "newest runtime observation, per authority rule " + rule.id;
171
+ status = "resolved";
172
+ }
173
+ }
174
+
175
+ if (!election.elected) {
176
+ election.rationale = election.rationale || (
177
+ rule
178
+ ? "authority rule " + rule.id + " does not resolve this disagreement"
179
+ : "no authority rule governs '" + key + "' for " + subject + "; fail closed"
180
+ );
181
+ }
182
+
183
+ for (const a of list) {
184
+ if (a === election.elected) a.status = "elected";
185
+ else if (a.confidence === "candidate") a.status = "candidate";
186
+ else a.status = "contradicted";
187
+ }
188
+ election.contradicted = list.filter((a) => a !== election.elected && a.confidence !== "candidate").map((a) => a.id);
189
+
190
+ const kind = classifyConflict(list, election, candidateOnly);
191
+ conflicts.push({
192
+ subject,
193
+ key,
194
+ kind,
195
+ status,
196
+ assertionIds: list.map((a) => a.id).sort(),
197
+ election,
198
+ });
199
+ elections.push({ subject, key, ruleId: election.ruleId, basis: election.basis, elected: election.elected });
200
+ }
201
+
202
+ return { conflicts, elections };
203
+ }
204
+
205
+ function classifyConflict(list, election, candidateOnly) {
206
+ const classes = new Set(list.map((a) => a.evidenceClass));
207
+ if (election.basis === "reviewed-override") return "reviewed-divergence";
208
+ // An observation that declares itself superseded no longer competes: a newer
209
+ // capture of the same authority system replaces it outright.
210
+ if (list.some((a) => a.observed && a.observed.supersedes)) return "supersession";
211
+ if (classes.has("OBSERVED_RUNTIME") && classes.has("AUTHORED")) return "contradiction";
212
+ if (classes.has("OBSERVED_RUNTIME") && classes.size === 1) return "staleness";
213
+ if (candidateOnly) return "ambiguity";
214
+ if (classes.size > 1) return "contradiction";
215
+ return "ambiguity";
216
+ }
217
+
218
+ export function distinctValues(list) {
219
+ const seen = new Map();
220
+ for (const a of list) {
221
+ const k = stable(a.value);
222
+ if (!seen.has(k)) seen.set(k, a.value);
223
+ }
224
+ return [...seen.values()];
225
+ }
226
+
227
+ function containsValue(big, small) {
228
+ if (Array.isArray(big) && Array.isArray(small)) {
229
+ return small.every((s) => big.some((b) => stable(b) === stable(s))) && big.length > small.length;
230
+ }
231
+ return false;
232
+ }
233
+
234
+ export function stable(v) {
235
+ if (v === null || typeof v !== "object") return JSON.stringify(v);
236
+ if (Array.isArray(v)) return "[" + v.map(stable).join(",") + "]";
237
+ return "{" + Object.keys(v).sort().map((k) => JSON.stringify(k) + ":" + stable(v[k])).join(",") + "}";
238
+ }
239
+
240
+ export function assertConfidence(c) {
241
+ if (!CONFIDENCE.includes(c)) throw new Error("unknown confidence " + c);
242
+ return c;
243
+ }
@@ -0,0 +1,41 @@
1
+ // Fact model.
2
+ //
3
+ // A "fact" is a claim about one (subject, key) pair. Multiple facts may exist
4
+ // for the same pair when different evidence classes disagree — that is a
5
+ // conflict, and it is surfaced, never reconciled silently.
6
+ //
7
+ // Well-known keys are namespaced by domain so an authority rule can be written
8
+ // per fact-kind rather than per project. See SPEC.md "Fact keys".
9
+ export const FACT_KEYS = Object.freeze({
10
+ // what a scheduler actually runs
11
+ "schedule.cron": "the cron expression a platform scheduler evaluates for this component's job",
12
+ // what a binding actually points at
13
+ "binding.kind": "the kind of platform resource a declared binding resolves to",
14
+ "binding.target": "the concrete instance a declared binding resolves to",
15
+ // what an external API actually accepts
16
+ "contract.supports": "capabilities an external contract actually accepts",
17
+ "contract.version": "the deployed version of a contract",
18
+ // component shape
19
+ "component.deployable": "whether the component ships as an independently deployable artifact",
20
+ "component.importable": "whether the component ships as an independently importable library",
21
+ "component.runtime": "the runtime the deployed artifact executes on",
22
+ "component.language": "the implementation language of the component",
23
+ // governance
24
+ "placement.system": "the system a component is placed in",
25
+ "placement.domain": "the domain a component or system is placed in",
26
+ "resource.ownership": "which component owns a logical resource",
27
+ "schedule.enabled": "whether a scheduled job is enabled in the scheduler",
28
+ "schedule.target": "the endpoint a scheduled job invokes",
29
+ });
30
+
31
+ export function isKnownFactKey(key) {
32
+ return Object.prototype.hasOwnProperty.call(FACT_KEYS, key);
33
+ }
34
+
35
+ export function knownFactKeys() {
36
+ return Object.keys(FACT_KEYS);
37
+ }
38
+
39
+ export function assertFactKeyShape(key) {
40
+ return typeof key === "string" && /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/.test(key) && key.length <= 80;
41
+ }
package/core/codes.mjs ADDED
@@ -0,0 +1,119 @@
1
+ // Stable diagnostic/error codes. Every failure the compiler or a gate can
2
+ // produce is named here so tests, CI and the skill can match on identity
3
+ // rather than on human-readable message text.
4
+ export const CODES = {
5
+ // source & schema
6
+ UTF8: "AGENTDOC_UTF8",
7
+ YAML_PARSE: "AGENTDOC_YAML_PARSE",
8
+ YAML_DUP_KEY: "AGENTDOC_YAML_DUP_KEY",
9
+ YAML_ANCHOR: "AGENTDOC_YAML_ANCHOR",
10
+ YAML_ALIAS: "AGENTDOC_YAML_ALIAS",
11
+ YAML_TAG: "AGENTDOC_YAML_TAG",
12
+ YAML_MERGE_KEY: "AGENTDOC_YAML_MERGE_KEY",
13
+ YAML_ENV: "AGENTDOC_YAML_ENV",
14
+ YAML_UNSUPPORTED: "AGENTDOC_YAML_UNSUPPORTED",
15
+ YAML_NON_MAP_ROOT: "AGENTDOC_YAML_NON_MAP_ROOT",
16
+ SCHEMA: "AGENTDOC_SCHEMA",
17
+ UNKNOWN_FIELD: "AGENTDOC_UNKNOWN_FIELD",
18
+ PROHIBITED_FIELD: "AGENTDOC_PROHIBITED_FIELD",
19
+ API_VERSION: "AGENTDOC_API_VERSION",
20
+ KIND: "AGENTDOC_KIND",
21
+ LOCATION: "AGENTDOC_LOCATION",
22
+ CONFIG: "AGENTDOC_CONFIG",
23
+ GLOB: "AGENTDOC_GLOB",
24
+ PATH_ESCAPE: "AGENTDOC_PATH_ESCAPE",
25
+ PATH_UNRESOLVED: "AGENTDOC_PATH_UNRESOLVED",
26
+ SECRET: "AGENTDOC_SECRET",
27
+
28
+ // identity & references
29
+ NAME_INVALID: "AGENTDOC_NAME_INVALID",
30
+ REF_INVALID: "AGENTDOC_REF_INVALID",
31
+ REF_UNRESOLVED: "AGENTDOC_REF_UNRESOLVED",
32
+ DUPLICATE_IDENTITY: "AGENTDOC_DUPLICATE_IDENTITY",
33
+
34
+ // discovery coverage
35
+ COVERAGE_ZERO: "AGENTDOC_COVERAGE_ZERO",
36
+ COVERAGE_MULTI: "AGENTDOC_COVERAGE_MULTI",
37
+ COMPONENT_UNCORROBORATED: "AGENTDOC_COMPONENT_UNCORROBORATED",
38
+ EVENT_CONTRACT: "AGENTDOC_EVENT_CONTRACT",
39
+ ARTIFACT_TYPE: "AGENTDOC_ARTIFACT_TYPE",
40
+
41
+ // contracts
42
+ CONTRACT_FORM: "AGENTDOC_CONTRACT_FORM",
43
+ CONTRACT_SYNTAX: "AGENTDOC_CONTRACT_SYNTAX",
44
+ CONTRACT_UNCLAIMED: "AGENTDOC_CONTRACT_UNCLAIMED",
45
+ CONTRACT_SHARED: "AGENTDOC_CONTRACT_SHARED",
46
+ OIDC_DISCOVERY: "AGENTDOC_OIDC_DISCOVERY",
47
+
48
+ // relations
49
+ RELATION_KIND: "AGENTDOC_RELATION_KIND",
50
+ RELATION_UNRESOLVED: "AGENTDOC_RELATION_UNRESOLVED",
51
+ RELATION_AUTHORED: "AGENTDOC_RELATION_AUTHORED",
52
+ RELATION_MISSING_INVERSE: "AGENTDOC_RELATION_MISSING_INVERSE",
53
+ RELATION_CONTRACT_DUP: "AGENTDOC_RELATION_CONTRACT_DUP",
54
+
55
+ // authority / conflicts
56
+ CONFLICT_UNRESOLVED: "AGENTDOC_CONFLICT_UNRESOLVED",
57
+ OBSERVATION_STALE: "AGENTDOC_OBSERVATION_STALE",
58
+ ELECTION_STALE: "AGENTDOC_ELECTION_STALE",
59
+ UNRESOLVED_FACT: "AGENTDOC_UNRESOLVED_FACT",
60
+
61
+ // provenance & determinism
62
+ PROVENANCE: "AGENTDOC_PROVENANCE",
63
+ NONDETERMINISTIC: "AGENTDOC_NONDETERMINISTIC",
64
+ GRAPH_SCHEMA: "AGENTDOC_GRAPH_SCHEMA",
65
+
66
+ // artifacts & gates
67
+ STALE_GRAPH: "AGENTDOC_STALE_GRAPH",
68
+ GRAPH_FRESHNESS: "AGENTDOC_GRAPH_FRESHNESS",
69
+ GRAPH_MISSING: "AGENTDOC_GRAPH_MISSING",
70
+ COMPILER_MISMATCH: "AGENTDOC_COMPILER_MISMATCH",
71
+ DIRTY_SOURCE: "AGENTDOC_DIRTY_SOURCE",
72
+ WARNING_ACCEPTANCE: "AGENTDOC_WARNING_ACCEPTANCE",
73
+ OVERRIDE_STALE: "AGENTDOC_OVERRIDE_STALE",
74
+ JOURNEY: "AGENTDOC_JOURNEY",
75
+ };
76
+
77
+ // Warning codes that a reviewer may explicitly accept. Anything outside this
78
+ // list is unacceptable: acceptance must never downgrade a hard error.
79
+ export const ACCEPTABLE_WARNING_CODES = Object.freeze([
80
+ "AGENTDOC_EVENT_UNRESOLVED",
81
+ "AGENTDOC_RESOURCE_CANDIDATE",
82
+ "AGENTDOC_SERVICE_BINDING_UNRESOLVED",
83
+ "AGENTDOC_SCHEDULE_UNRESOLVED",
84
+ "AGENTDOC_BINDING_AMBIGUOUS",
85
+ "AGENTDOC_CONFLICT_REVIEWED",
86
+ "AGENTDOC_OBSERVATION_SUPERSEDED",
87
+ "AGENTDOC_EXTERNAL_DEPENDENCY_UNCLASSIFIED",
88
+ "AGENTDOC_DOC_ORPHANED",
89
+ "AGENTDOC_VERIFICATION_UNTIERED",
90
+ ]);
91
+
92
+ export class AgentDocError extends Error {
93
+ constructor(code, message, opts = {}) {
94
+ super(message);
95
+ this.name = "AgentDocError";
96
+ this.code = code;
97
+ this.path = opts.path;
98
+ this.ref = opts.ref;
99
+ this.line = opts.line;
100
+ }
101
+ format() {
102
+ const where = [this.path, this.line ? "line " + this.line : null, this.ref].filter(Boolean).join(" ");
103
+ return this.code + (where ? " [" + where + "]" : "") + ": " + this.message;
104
+ }
105
+ }
106
+
107
+ // Run fn, pushing any AgentDocError into errors and returning undefined.
108
+ // A non-AgentDocError is a compiler defect and is rethrown.
109
+ export function collect(fn, errors) {
110
+ try {
111
+ return fn();
112
+ } catch (e) {
113
+ if (e instanceof AgentDocError) {
114
+ errors.push(e);
115
+ return undefined;
116
+ }
117
+ throw e;
118
+ }
119
+ }