@sourcey/catalog-verifier 1.1.21 → 1.1.23

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 (149) hide show
  1. package/README.md +4 -1
  2. package/SOURCEY-CLOSURE.json +265 -121
  3. package/dist/contracts/agent-readiness/src/binding.d.ts +28 -1
  4. package/dist/contracts/agent-readiness/src/binding.d.ts.map +1 -1
  5. package/dist/contracts/agent-readiness/src/binding.js +75 -16
  6. package/dist/contracts/agent-readiness/src/binding.js.map +1 -1
  7. package/dist/contracts/agent-readiness/src/client.d.ts +30 -0
  8. package/dist/contracts/agent-readiness/src/client.d.ts.map +1 -0
  9. package/dist/contracts/agent-readiness/src/client.js +25 -0
  10. package/dist/contracts/agent-readiness/src/client.js.map +1 -0
  11. package/dist/contracts/agent-readiness/src/declaration-acquisition.d.ts +91 -0
  12. package/dist/contracts/agent-readiness/src/declaration-acquisition.d.ts.map +1 -1
  13. package/dist/contracts/agent-readiness/src/declaration-acquisition.js +6 -0
  14. package/dist/contracts/agent-readiness/src/declaration-acquisition.js.map +1 -1
  15. package/dist/contracts/agent-readiness/src/declaration.d.ts +358 -0
  16. package/dist/contracts/agent-readiness/src/declaration.d.ts.map +1 -1
  17. package/dist/contracts/agent-readiness/src/declaration.js +34 -79
  18. package/dist/contracts/agent-readiness/src/declaration.js.map +1 -1
  19. package/dist/contracts/agent-readiness/src/{operate-policy.d.ts → grade-policy.d.ts} +31 -79
  20. package/dist/contracts/agent-readiness/src/grade-policy.d.ts.map +1 -0
  21. package/dist/contracts/agent-readiness/src/grade-policy.js +93 -0
  22. package/dist/contracts/agent-readiness/src/grade-policy.js.map +1 -0
  23. package/dist/contracts/agent-readiness/src/index.d.ts +3 -1
  24. package/dist/contracts/agent-readiness/src/index.d.ts.map +1 -1
  25. package/dist/contracts/agent-readiness/src/index.js +3 -1
  26. package/dist/contracts/agent-readiness/src/index.js.map +1 -1
  27. package/dist/contracts/agent-readiness/src/jobs.d.ts +12 -3
  28. package/dist/contracts/agent-readiness/src/jobs.d.ts.map +1 -1
  29. package/dist/contracts/agent-readiness/src/jobs.js +8 -1
  30. package/dist/contracts/agent-readiness/src/jobs.js.map +1 -1
  31. package/dist/contracts/agent-readiness/src/offer-relations.d.ts +89 -0
  32. package/dist/contracts/agent-readiness/src/offer-relations.d.ts.map +1 -1
  33. package/dist/contracts/agent-readiness/src/operation.d.ts +106 -0
  34. package/dist/contracts/agent-readiness/src/operation.d.ts.map +1 -0
  35. package/dist/contracts/agent-readiness/src/operation.js +228 -0
  36. package/dist/contracts/agent-readiness/src/operation.js.map +1 -0
  37. package/dist/contracts/agent-readiness/src/release-index.d.ts +277 -89
  38. package/dist/contracts/agent-readiness/src/release-index.d.ts.map +1 -1
  39. package/dist/contracts/agent-readiness/src/revision.d.ts +500 -166
  40. package/dist/contracts/agent-readiness/src/revision.d.ts.map +1 -1
  41. package/dist/contracts/agent-readiness/src/revision.js +100 -37
  42. package/dist/contracts/agent-readiness/src/revision.js.map +1 -1
  43. package/dist/contracts/agent-readiness/src/run.d.ts +386 -98
  44. package/dist/contracts/agent-readiness/src/run.d.ts.map +1 -1
  45. package/dist/contracts/agent-readiness/src/run.js +333 -52
  46. package/dist/contracts/agent-readiness/src/run.js.map +1 -1
  47. package/dist/contracts/agent-readiness/src/shared.d.ts +51 -17
  48. package/dist/contracts/agent-readiness/src/shared.d.ts.map +1 -1
  49. package/dist/contracts/agent-readiness/src/shared.js +52 -23
  50. package/dist/contracts/agent-readiness/src/shared.js.map +1 -1
  51. package/dist/contracts/agent-readiness/src/surfaces.d.ts +25 -0
  52. package/dist/contracts/agent-readiness/src/surfaces.d.ts.map +1 -0
  53. package/dist/contracts/agent-readiness/src/surfaces.js +76 -0
  54. package/dist/contracts/agent-readiness/src/surfaces.js.map +1 -0
  55. package/dist/contracts/api/src/endpoints.d.ts +885 -254
  56. package/dist/contracts/api/src/endpoints.d.ts.map +1 -1
  57. package/dist/contracts/api/src/read.d.ts +354 -109
  58. package/dist/contracts/api/src/read.d.ts.map +1 -1
  59. package/dist/contracts/api/src/read.js +1 -1
  60. package/dist/contracts/api/src/routes.d.ts +27 -23
  61. package/dist/contracts/api/src/routes.d.ts.map +1 -1
  62. package/dist/contracts/api/src/routes.js +2 -3
  63. package/dist/contracts/api/src/routes.js.map +1 -1
  64. package/dist/contracts/api/src/submissions.d.ts +91 -0
  65. package/dist/contracts/api/src/submissions.d.ts.map +1 -1
  66. package/dist/contracts/datasets/src/index.d.ts +13 -10
  67. package/dist/contracts/datasets/src/index.d.ts.map +1 -1
  68. package/dist/contracts/startup-credits-commercial/src/product.js +1 -1
  69. package/dist/contracts/startup-credits-commercial/src/product.js.map +1 -1
  70. package/dist/modules/agent-readiness-engine/src/assertions.d.ts.map +1 -1
  71. package/dist/modules/agent-readiness-engine/src/assertions.js +25 -0
  72. package/dist/modules/agent-readiness-engine/src/assertions.js.map +1 -1
  73. package/dist/modules/agent-readiness-engine/src/authority.d.ts +81 -0
  74. package/dist/modules/agent-readiness-engine/src/authority.d.ts.map +1 -0
  75. package/dist/modules/agent-readiness-engine/src/authority.js +189 -0
  76. package/dist/modules/agent-readiness-engine/src/authority.js.map +1 -0
  77. package/dist/modules/agent-readiness-engine/src/challenges.d.ts +13 -0
  78. package/dist/modules/agent-readiness-engine/src/challenges.d.ts.map +1 -0
  79. package/dist/modules/agent-readiness-engine/src/challenges.js +67 -0
  80. package/dist/modules/agent-readiness-engine/src/challenges.js.map +1 -0
  81. package/dist/modules/agent-readiness-engine/src/documents.d.ts +21 -0
  82. package/dist/modules/agent-readiness-engine/src/documents.d.ts.map +1 -0
  83. package/dist/modules/agent-readiness-engine/src/documents.js +69 -0
  84. package/dist/modules/agent-readiness-engine/src/documents.js.map +1 -0
  85. package/dist/modules/agent-readiness-engine/src/naming.d.ts +19 -0
  86. package/dist/modules/agent-readiness-engine/src/naming.d.ts.map +1 -0
  87. package/dist/modules/agent-readiness-engine/src/naming.js +28 -0
  88. package/dist/modules/agent-readiness-engine/src/naming.js.map +1 -0
  89. package/dist/modules/agent-readiness-engine/src/path.d.ts +68 -15
  90. package/dist/modules/agent-readiness-engine/src/path.d.ts.map +1 -1
  91. package/dist/modules/agent-readiness-engine/src/path.js +467 -153
  92. package/dist/modules/agent-readiness-engine/src/path.js.map +1 -1
  93. package/dist/modules/agent-readiness-engine/src/profile.d.ts +85 -0
  94. package/dist/modules/agent-readiness-engine/src/profile.d.ts.map +1 -0
  95. package/dist/modules/agent-readiness-engine/src/profile.js +176 -0
  96. package/dist/modules/agent-readiness-engine/src/profile.js.map +1 -0
  97. package/dist/modules/agent-readiness-engine/src/rate.d.ts +10 -20
  98. package/dist/modules/agent-readiness-engine/src/rate.d.ts.map +1 -1
  99. package/dist/modules/agent-readiness-engine/src/rate.js +29 -47
  100. package/dist/modules/agent-readiness-engine/src/rate.js.map +1 -1
  101. package/dist/modules/agent-readiness-engine/src/records.d.ts +30 -0
  102. package/dist/modules/agent-readiness-engine/src/records.d.ts.map +1 -0
  103. package/dist/modules/agent-readiness-engine/src/records.js +59 -0
  104. package/dist/modules/agent-readiness-engine/src/records.js.map +1 -0
  105. package/dist/modules/agent-readiness-jobs/src/index.d.ts +16 -8
  106. package/dist/modules/agent-readiness-jobs/src/index.d.ts.map +1 -1
  107. package/dist/modules/agent-readiness-jobs/src/index.js +12 -7
  108. package/dist/modules/agent-readiness-jobs/src/index.js.map +1 -1
  109. package/dist/modules/agent-readiness-policy/src/current-policy.d.ts +13 -17
  110. package/dist/modules/agent-readiness-policy/src/current-policy.d.ts.map +1 -1
  111. package/dist/modules/agent-readiness-policy/src/current-policy.js +1 -1
  112. package/dist/modules/agent-readiness-policy/src/current-policy.js.map +1 -1
  113. package/dist/modules/agent-readiness-policy/src/{operate-policy.d.ts → grade-policy.d.ts} +3 -3
  114. package/dist/modules/agent-readiness-policy/src/grade-policy.d.ts.map +1 -0
  115. package/dist/modules/agent-readiness-policy/src/grade-policy.js +82 -0
  116. package/dist/modules/agent-readiness-policy/src/grade-policy.js.map +1 -0
  117. package/dist/modules/agent-readiness-policy/src/index.d.ts +4 -4
  118. package/dist/modules/agent-readiness-policy/src/index.d.ts.map +1 -1
  119. package/dist/modules/agent-readiness-policy/src/index.js +35 -22
  120. package/dist/modules/agent-readiness-policy/src/index.js.map +1 -1
  121. package/dist/modules/agent-readiness-policy/src/revision.d.ts +3 -3
  122. package/dist/modules/agent-readiness-policy/src/revision.d.ts.map +1 -1
  123. package/dist/modules/agent-readiness-policy/src/revision.js +40 -32
  124. package/dist/modules/agent-readiness-policy/src/revision.js.map +1 -1
  125. package/dist/modules/artifact/src/policy-closure.js +1 -1
  126. package/dist/modules/artifact/src/policy-closure.js.map +1 -1
  127. package/dist/modules/catalog-admission/src/publication-dependencies.d.ts +0 -4
  128. package/dist/modules/catalog-admission/src/publication-dependencies.d.ts.map +1 -1
  129. package/dist/modules/catalog-admission/src/publication-dependencies.js +1 -1
  130. package/dist/modules/catalog-admission/src/publication-dependencies.js.map +1 -1
  131. package/dist/modules/release/src/identity.d.ts +0 -1
  132. package/dist/modules/release/src/identity.d.ts.map +1 -1
  133. package/dist/modules/release/src/identity.js +1 -1
  134. package/dist/modules/release/src/identity.js.map +1 -1
  135. package/dist/modules/release/src/verification-policies.d.ts +13 -17
  136. package/dist/modules/release/src/verification-policies.d.ts.map +1 -1
  137. package/dist/modules/release/src/verification-policies.js +1 -1
  138. package/dist/modules/release/src/verification-policies.js.map +1 -1
  139. package/dist/modules/sourcey-publication/src/verifier.d.ts +5 -0
  140. package/dist/modules/sourcey-publication/src/verifier.d.ts.map +1 -1
  141. package/dist/modules/sourcey-publication/src/verifier.js +13 -0
  142. package/dist/modules/sourcey-publication/src/verifier.js.map +1 -1
  143. package/package.json +1 -1
  144. package/dist/contracts/agent-readiness/src/operate-policy.d.ts.map +0 -1
  145. package/dist/contracts/agent-readiness/src/operate-policy.js +0 -86
  146. package/dist/contracts/agent-readiness/src/operate-policy.js.map +0 -1
  147. package/dist/modules/agent-readiness-policy/src/operate-policy.d.ts.map +0 -1
  148. package/dist/modules/agent-readiness-policy/src/operate-policy.js +0 -110
  149. package/dist/modules/agent-readiness-policy/src/operate-policy.js.map +0 -1
@@ -1,59 +1,325 @@
1
1
  import { compareInstants } from "provenry/primitives";
2
2
  import { AGENT_READINESS_STEPS, } from "../../../contracts/agent-readiness/src/index.js";
3
+ import { consentReached } from "./authority.js";
4
+ import { agentReadinessFactNamesEndpoint, agentReadinessNamesEndpoint } from "./naming.js";
5
+ import { probeUnanswered } from "./records.js";
3
6
  const MAXIMUM_EVIDENCE = 32;
4
- /** Facts that name an interface an agent can call; others (metadata, registration) do not. */
5
- const NAMING_FACTS = new Set([
6
- "mcp_endpoint",
7
- "ard_entry",
8
- "api_catalog_link",
9
- "openapi_server",
10
- "oauth_protected_resource",
11
- ]);
7
+ const DAY_MS = 24 * 60 * 60 * 1_000;
12
8
  /**
13
- * The Operate path a profile's runs establish: one outcome per step, each
14
- * resting on entries of the runs. The latest run decides every step; earlier
15
- * runs count only to reproduce a refusal, the one way a step is `blocked`.
16
- * Documentation never enters: only exchanges, discovery attempts, recorded
17
- * credentials and recorded human steps.
9
+ * The probed path of one interface (north star §5.6). The latest probe run that
10
+ * answered the operation decides its steps; a run that left its request, its
11
+ * malformed request, its spec, its authority chain or a declared page
12
+ * unanswered decides nothing, since residue is never a fact. Discovery's
13
+ * latest run decides Discover and the payment manifests, unless an unanswered
14
+ * read could change them: then the latest run whose reads were all answered,
15
+ * or left unanswered only the service's standing answers, does. Earlier runs
16
+ * count only to reproduce a page answer or a standing answer.
17
+ */
18
+ export function evaluateProbePath(input) {
19
+ const runs = oldestFirst(input.runs);
20
+ const latest = runs.at(-1);
21
+ if (!latest)
22
+ throw new Error("A probed path needs at least one probe run.");
23
+ const { operation } = input;
24
+ const id = operation.operation_id;
25
+ const answeredRuns = runs.filter((run) => !probeUnanswered(run, id));
26
+ const current = answeredRuns.at(-1);
27
+ const probe = current?.probes.find(({ operation_id }) => operation_id === id);
28
+ if (!current || !probe) {
29
+ return {
30
+ steps: AGENT_READINESS_STEPS.map(notAssessed),
31
+ job: NOT_ASSESSED_JOB,
32
+ reproducible: false,
33
+ discoveryRun: null,
34
+ verifiedRun: null,
35
+ standingRuns: [],
36
+ };
37
+ }
38
+ const answer = probe.request?.answer ?? null;
39
+ const of = (purpose) => exchangeEvidence(current, (entry) => entry.call_id === id && entry.purpose === purpose);
40
+ const requested = of("probe_operation");
41
+ const malformed = of("probe_malformed");
42
+ const metadata = of("probe_authorization_server");
43
+ const chainEvidence = [
44
+ ...of("probe_resource_metadata"),
45
+ ...metadata,
46
+ ...of("probe_client_registration"),
47
+ ...of("probe_authorization"),
48
+ ];
49
+ const pagesOf = (include) => {
50
+ const ids = new Set(current.pages.filter(include).map(({ page_id }) => page_id));
51
+ return exchangeEvidence(current, (entry) => entry.purpose === "probe_page" && ids.has(entry.call_id));
52
+ };
53
+ const spec = probe.spec;
54
+ const specRead = spec?.fetched === "read" ? spec : null;
55
+ const covered = specRead?.covers === true ? specRead : null;
56
+ const specEvidence = spec
57
+ ? [{ kind: "discovery", run_digest: current.run_digest, attempt_digest: spec.attempt_digest }]
58
+ : [];
59
+ const declaration = () => [{ kind: "declaration", run_digest: current.run_digest }];
60
+ const chain = probe.authority;
61
+ const refusal = answer === "offer" || answer === "authority" || answer === "error";
62
+ // No account is needed: the request itself answered, or the spec requires no security.
63
+ const anonymous = answer === "success" ||
64
+ answer === "offer" ||
65
+ ((answer === null || answer === "error" || answer === "page") &&
66
+ covered !== null &&
67
+ covered.security.includes("none"));
68
+ // An agent registration path: the chain's auth.md registration, or a sign-up API the listing names.
69
+ // A sign-up API answers the probe's GET as an API that asks nothing of a person: a success, an
70
+ // offer, or a typed 405 (it signs up by POST). A CAPTCHA or a credential demanded is for people.
71
+ const pageStatus = (page) => {
72
+ const entry = current.exchanges.find(({ purpose, call_id }) => purpose === "probe_page" && call_id === page.page_id);
73
+ return entry?.record.outcome === "responded" ? entry.record.response.status : null;
74
+ };
75
+ const signUpApi = (page) => page.role === "registration" &&
76
+ page.found &&
77
+ (page.answer === "success" ||
78
+ page.answer === "offer" ||
79
+ (page.answer === "error" && pageStatus(page) === 405));
80
+ const signedUp = current.pages.some(signUpApi);
81
+ const registration = chain?.registration === "agent" || signedUp ? "agent" : (chain?.registration ?? null);
82
+ const registrationEvidence = [
83
+ ...(chain?.registration ? chainEvidence : []),
84
+ ...(signedUp ? pagesOf(signUpApi) : []),
85
+ ];
86
+ const target = operation.kind === "http" ? operation.url : input.endpoint;
87
+ // A read unanswered on every probe since it last answered, the first of them the policy's
88
+ // interval before the latest, is the service's standing answer. A run that answered it, or did
89
+ // not read it, starts the count again.
90
+ const stretches = latest.discovery.unanswered.map((url) => {
91
+ const since = runs.findLastIndex(({ discovery }) => !discovery.unanswered.includes(url));
92
+ return { url, runs: reproducing(runs.slice(since + 1), input.policy.reproduction) };
93
+ });
94
+ const standing = new Set(stretches.flatMap(({ url, runs: found }) => (found.length > 0 ? [url] : [])));
95
+ const settled = (run) => run.discovery.unanswered.every((url) => standing.has(url));
96
+ const settledDiscovery = [...runs].reverse().find(settled);
97
+ /** The discovery run that decides: the latest, unless a read there still unanswered could change the reading. */
98
+ const discoveryFor = (decided) => decided(latest) || settled(latest) ? latest : (settledDiscovery ?? null);
99
+ // A server that answers for authority before listing its tools describes them to the agent it
100
+ // authorizes: it asked before any tool was called. A list it refused or cut short describes none.
101
+ const toolCalled = current.exchanges.some((entry) => entry.call_id === id && entry.purpose === "probe_operation" && entry.role === "request");
102
+ const toolDescribed = operation.kind === "mcp" &&
103
+ (probe.tool_listed === true ||
104
+ (probe.tool_listed === null && answer === "authority" && !toolCalled));
105
+ const finds = (run) => discoverStep(run, current, operation, target, covered, specEvidence, toolDescribed);
106
+ const discoveryRun = discoveryFor((candidate) => finds(candidate).outcome === "machine");
107
+ const discover = discoveryRun ? finds(discoveryRun) : notAssessed("discover");
108
+ const authority = (() => {
109
+ if (anonymous) {
110
+ return result("authority", "not_applicable", null, answer === null ? specEvidence : requested);
111
+ }
112
+ if (registration !== null) {
113
+ return result("authority", "machine", null, [...requested, ...registrationEvidence]);
114
+ }
115
+ const authorization = current.exchanges.find((entry) => entry.call_id === id && entry.purpose === "probe_authorization");
116
+ if (chain?.client && authorization && consentReached(authorization.record)) {
117
+ return result("authority", "approval", "setup", [...requested, ...chainEvidence]);
118
+ }
119
+ // A key or token scheme, an OAuth flow the agent's own client cannot start,
120
+ // or nothing machine-readable: a person hands over a credential.
121
+ return result("authority", "workaround", "setup", [
122
+ ...requested,
123
+ ...chainEvidence,
124
+ ...specEvidence,
125
+ ]);
126
+ })();
127
+ const onboard = (() => {
128
+ if (anonymous)
129
+ return result("onboard", "not_applicable", null, authority.evidence);
130
+ // The vendor's decision stands before any way in: whatever registration exists, it decides.
131
+ const accessPage = pagesOf(({ page_id, found }) => input.accessGates.includes(page_id) && found);
132
+ if (accessPage.length > 0)
133
+ return result("onboard", "gated", null, accessPage);
134
+ // A setting a person on the account turns on once before the agent can use this interface (an
135
+ // organization's MCP write access): a decision made once, whatever way in the agent has.
136
+ const turnedOn = pagesOf(({ page_id, found }) => input.accessSettings.includes(page_id) && found);
137
+ const agentGetsIn = (evidence) => turnedOn.length > 0
138
+ ? result("onboard", "approval", "setup", [...evidence, ...turnedOn])
139
+ : result("onboard", "machine", null, evidence);
140
+ if (registration === "agent")
141
+ return agentGetsIn(registrationEvidence);
142
+ if (registration === "claimable") {
143
+ return result("onboard", "approval", "setup", [...registrationEvidence, ...turnedOn]);
144
+ }
145
+ // The agent's own OAuth client gets it in once the server takes it to consent (the shape's A).
146
+ if (chain?.client && authority.outcome === "approval")
147
+ return agentGetsIn(chainEvidence);
148
+ // No path for the agent: what the probes found, or the listing itself when they sent nothing.
149
+ const evidence = [
150
+ ...requested,
151
+ ...pagesOf(({ role }) => role === "registration"),
152
+ ...chainEvidence,
153
+ ...specEvidence,
154
+ ];
155
+ return result("onboard", "workaround", "setup", evidence.length > 0 ? evidence : declaration());
156
+ })();
157
+ const pay = (() => {
158
+ if (answer === "offer")
159
+ return result("pay", "machine", null, requested);
160
+ if (covered?.payment)
161
+ return result("pay", "machine", null, specEvidence);
162
+ const manifests = (run) => target === null
163
+ ? []
164
+ : run.discovery.facts.filter((found) => found.fact === "payment_manifest" &&
165
+ ownDomain(found.descriptor_url, run.discovery.domains) &&
166
+ agentReadinessNamesEndpoint(found.value, target));
167
+ const run = discoveryFor((candidate) => manifests(candidate).length > 0);
168
+ const named = run ? manifests(run) : [];
169
+ if (named.length > 0 && run) {
170
+ return result("pay", "machine", null, named.map(({ attempt_digest }) => ({
171
+ kind: "discovery",
172
+ run_digest: run.run_digest,
173
+ attempt_digest,
174
+ })));
175
+ }
176
+ if (anonymous) {
177
+ return result("pay", "not_applicable", null, answer === null ? specEvidence : requested);
178
+ }
179
+ if (input.checkoutRuledOut)
180
+ return result("pay", "not_applicable", null, declaration());
181
+ // An unanswered read could have held a manifest that names the endpoint.
182
+ if (!run)
183
+ return notAssessed("pay");
184
+ // A dead or invented page sets nothing: the checkout counts once it is found.
185
+ const checkout = pagesOf(({ role, found }) => role === "checkout" && found);
186
+ return checkout.length > 0
187
+ ? result("pay", "workaround", "setup", checkout)
188
+ : notAssessed("pay");
189
+ })();
190
+ const pages = answeredRuns.filter((run) => run.probes.find(({ operation_id }) => operation_id === id)?.request?.answer === "page");
191
+ const pageRuns = answer === "page" ? reproducing(pages, input.policy.reproduction) : [];
192
+ const pageReproduced = pageRuns.length > 0;
193
+ const job = (() => {
194
+ // The job's tool answered, or asked for the authority it needs: whatever an anonymous list
195
+ // says, the server performs it for an agent with that authority.
196
+ if (answer === "success" || answer === "offer" || answer === "authority") {
197
+ return jobResult("reachable", requested);
198
+ }
199
+ // The server lists its tools, the job's is not among them, and its call is refused with an error.
200
+ if (operation.kind === "mcp" && probe.tool_listed === false && answer === "error") {
201
+ return jobResult("unavailable", requested);
202
+ }
203
+ if (refusal)
204
+ return jobResult("reachable", requested);
205
+ if (covered)
206
+ return jobResult("reachable", specEvidence);
207
+ if (answer === null)
208
+ return jobResult("unavailable", specEvidence);
209
+ if (pageReproduced) {
210
+ return jobResult("unavailable", pageRuns.flatMap((run) => exchangeEvidence(run, (entry) => entry.call_id === id && entry.purpose === "probe_operation")));
211
+ }
212
+ return NOT_ASSESSED_JOB;
213
+ })();
214
+ const confirm = (() => {
215
+ const read = refusal ? probe.request : answer === "success" ? probe.malformed : null;
216
+ if (read) {
217
+ const typedRefusal = (read.answer === "offer" || read.answer === "authority" || read.answer === "error") &&
218
+ read.typed === true;
219
+ const evidence = refusal ? requested : [...requested, ...malformed];
220
+ return typedRefusal
221
+ ? result("confirm", "machine", null, evidence)
222
+ : result("confirm", "workaround", "recurring", evidence);
223
+ }
224
+ // A page or a spec operation: the errors the spec declares for the job.
225
+ return covered?.typed_errors
226
+ ? result("confirm", "machine", null, specEvidence)
227
+ : result("confirm", "workaround", "recurring", [...requested, ...specEvidence]);
228
+ })();
229
+ const sustain = (() => {
230
+ if (authority.outcome === "not_applicable") {
231
+ return result("sustain", "not_applicable", null, authority.evidence);
232
+ }
233
+ // Either alternative keeps the agent's authority alive: its key operations, or the chain's
234
+ // grants, whoever obtained the first token.
235
+ if (covered?.key_operations)
236
+ return result("sustain", "machine", null, specEvidence);
237
+ if (chain !== null) {
238
+ if (!chain.renewal)
239
+ return result("sustain", "workaround", "recurring", metadata);
240
+ return chain.revocation
241
+ ? result("sustain", "machine", null, metadata)
242
+ : result("sustain", "workaround", "setup", metadata);
243
+ }
244
+ return result("sustain", "workaround", "setup", [...authority.evidence, ...specEvidence]);
245
+ })();
246
+ return {
247
+ steps: [discover, authority, onboard, pay, confirm, sustain],
248
+ job,
249
+ reproducible: answer === "page" && !pageReproduced && covered === null,
250
+ discoveryRun,
251
+ verifiedRun: null,
252
+ standingRuns: stretches.flatMap(({ runs: found }) => found),
253
+ };
254
+ }
255
+ /**
256
+ * The verified path of one binding: what performing the job with its authority
257
+ * establishes (north star §5.6, precedence). Only the vendor's own runs can
258
+ * lower a letter: a refusal of authority a vendor run proved valid counts when
259
+ * the vendor's latest run holds it and its earlier runs reproduce it (the
260
+ * job's refusal makes the job `unavailable`, Authority's makes that step
261
+ * `gated`, and a 402 is a balance a person tops up: Pay's recurring
262
+ * `workaround`), and so do failed assertions reproduced the same way (the job
263
+ * `unavailable`). Otherwise the latest run that did the job decides while it
264
+ * is fresh; a failing run from anyone else never lowers a letter.
18
265
  */
19
266
  export function evaluatePath(input) {
20
- const runs = [...input.runs].sort((left, right) => compareInstants(left.finished_at, right.finished_at) ||
21
- (left.run_digest < right.run_digest ? -1 : 1));
22
- const current = runs.at(-1);
23
- if (!current)
24
- throw new Error("A path needs at least one run.");
267
+ const runs = oldestFirst(input.runs);
268
+ const latestRun = runs.at(-1);
269
+ if (!latestRun)
270
+ throw new Error("A verified path needs at least one run.");
25
271
  const { job, binding } = input;
272
+ const { reproduction: rule } = input.policy;
26
273
  const succeeded = (run) => run.assertions.length === job.assertions.length &&
27
274
  job.assertions.every(({ name }) => run.assertions.some(({ assertion, holds }) => assertion === name && holds));
28
- const ok = succeeded(current);
275
+ const fresh = (run) => Date.parse(input.asOf) - Date.parse(run.finished_at) <= input.policy.fresh_for_days * DAY_MS;
276
+ const vendorRuns = runs.filter(({ label }) => label === "vendor_run_verified");
277
+ const holds = (found) => found?.latest === true && reproduced(found.runs, rule);
278
+ // Runs whose job completed and an assertion did not hold: the API exists but does not do the job.
279
+ const failedRun = (run) => run.assertions.length > 0 && run.assertions.some(({ holds: held }) => !held);
280
+ const vendorLatest = vendorRuns.at(-1);
281
+ const jobRefusals = refusals(vendorRuns, "job", rule.statuses);
282
+ const payRefusals = refusals(vendorRuns, "job", [402]);
283
+ const authorityRefusals = refusals(vendorRuns, "delegation", rule.statuses);
284
+ const failures = vendorRuns.filter(failedRun);
285
+ const failing = vendorLatest !== undefined && failedRun(vendorLatest);
286
+ const lowered = holds(jobRefusals) ||
287
+ holds(payRefusals) ||
288
+ holds(authorityRefusals) ||
289
+ (failing && reproduced(failures, rule));
290
+ // The run that decides: the vendor's, when its runs reproduce a failure; else the latest fresh success.
291
+ const current = (lowered ? vendorLatest : [...runs].reverse().find((run) => succeeded(run) && fresh(run))) ??
292
+ vendorLatest ??
293
+ latestRun;
294
+ const ok = !lowered && succeeded(current) && fresh(current);
29
295
  const jobEvidence = exchangeEvidence(current, (entry) => entry.purpose === "job");
30
- const refusal = (purpose) => reproducedRefusal(runs, purpose, input.policy.blocked);
31
- const jobRefusal = ok ? null : refusal("job");
32
- const jobStep = (() => {
296
+ const jobOutcome = (() => {
33
297
  if (ok)
34
- return result("job", "machine", null, jobEvidence);
35
- if (jobRefusal && jobRefusal.status !== 402) {
36
- return result("job", "blocked", null, jobRefusal.evidence);
298
+ return jobResult("verified", jobEvidence);
299
+ if (holds(jobRefusals))
300
+ return jobResult("unavailable", jobRefusals.evidence);
301
+ if (failing && reproduced(failures, rule)) {
302
+ return jobResult("unavailable", failures.flatMap((run) => exchangeEvidence(run, (entry) => entry.purpose === "job")));
37
303
  }
38
- return notAssessed("job");
304
+ return NOT_ASSESSED_JOB;
39
305
  })();
40
306
  const usedRoles = [...new Set(binding.calls.flatMap(({ credentials }) => credentials))];
41
- const delegationStep = (() => {
307
+ const authorityStep = (() => {
42
308
  if (binding.delegation.kind === "none") {
43
309
  return ok && usedRoles.length === 0
44
- ? result("delegation", "not_applicable", null, jobEvidence)
45
- : notAssessed("delegation");
310
+ ? result("authority", "not_applicable", null, jobEvidence)
311
+ : notAssessed("authority");
312
+ }
313
+ if (holds(authorityRefusals)) {
314
+ return result("authority", "gated", null, authorityRefusals.evidence);
46
315
  }
47
- const delegationRefusal = refusal("delegation");
48
- if (delegationRefusal)
49
- return result("delegation", "blocked", null, delegationRefusal.evidence);
50
316
  // The authority the job actually carried: the handles on its own requests.
51
317
  const carried = new Set(current.exchanges.flatMap((entry) => entry.purpose === "job" && entry.role === "request"
52
318
  ? entry.record.request.credentials.map(({ handle_digest }) => handle_digest)
53
319
  : []));
54
320
  const held = current.credentials.filter(({ handle_digest }) => carried.has(handle_digest));
55
321
  if (usedRoles.length === 0 || held.length === 0 || held.length !== carried.size) {
56
- return notAssessed("delegation");
322
+ return notAssessed("authority");
57
323
  }
58
324
  const evidence = held.map((source) => ({
59
325
  kind: "credential",
@@ -63,19 +329,27 @@ export function evaluatePath(input) {
63
329
  const issuedBy = new Set(held.flatMap(({ source }) => (source.kind === "issued" ? [source.exchange_digest] : [])));
64
330
  evidence.push(...exchangeEvidence(current, (entry) => issuedBy.has(entry.record.exchange_digest)));
65
331
  if (held.some(({ source }) => source.kind === "entered")) {
66
- return result("delegation", "workaround", "setup", evidence);
332
+ return result("authority", "workaround", "setup", evidence);
67
333
  }
68
334
  return binding.delegation.kind === "oauth"
69
- ? result("delegation", "approval", "setup", evidence)
70
- : result("delegation", "machine", null, evidence);
335
+ ? result("authority", "approval", "setup", evidence)
336
+ : result("authority", "machine", null, evidence);
71
337
  })();
72
338
  const payStep = (() => {
73
- if (jobRefusal?.status === 402)
74
- return result("pay", "blocked", null, jobRefusal.evidence);
75
- if (binding.payment.kind === "none") {
76
- return ok ? result("pay", "not_applicable", null, jobEvidence) : notAssessed("pay");
339
+ if (holds(payRefusals)) {
340
+ return result("pay", "workaround", "recurring", payRefusals.evidence);
341
+ }
342
+ if (!ok)
343
+ return notAssessed("pay");
344
+ switch (binding.payment.kind) {
345
+ case "none":
346
+ return result("pay", "not_applicable", null, jobEvidence);
347
+ case "per_call":
348
+ return result("pay", "machine", null, jobEvidence);
349
+ case "prepaid_balance":
350
+ // A balance a person set up: the run leaves Pay to the probes.
351
+ return notAssessed("pay");
77
352
  }
78
- return human(current, "pay", ok ? jobEvidence : []) ?? notAssessed("pay");
79
353
  })();
80
354
  const confirmStep = (() => {
81
355
  if (!ok || current.error_probe === null)
@@ -88,61 +362,107 @@ export function evaluatePath(input) {
88
362
  ? result("confirm", "machine", null, evidence)
89
363
  : result("confirm", "workaround", "recurring", evidence);
90
364
  })();
91
- const discoverStep = (() => {
92
- const { attempts, facts } = current.discovery;
93
- if (attempts.length === 0)
94
- return notAssessed("discover");
95
- const used = binding.calls.flatMap(({ endpoint_id }) => {
96
- const uri = input.endpoints.get(endpoint_id);
97
- return uri ? [uri] : [];
98
- });
99
- const named = facts.filter((fact) => NAMING_FACTS.has(fact.fact) && used.some((uri) => namesEndpoint(fact.value, uri)));
100
- const evidence = (named.length > 0 ? named.map(({ attempt_digest }) => attempt_digest) : attempts).map((attempt) => ({
101
- kind: "discovery",
102
- run_digest: current.run_digest,
103
- attempt_digest: attempt,
104
- }));
105
- return named.length > 0
106
- ? result("discover", "machine", null, evidence)
107
- : result("discover", "workaround", "setup", evidence);
108
- })();
109
365
  const sustainStep = (() => {
110
366
  if (binding.delegation.kind === "none") {
111
367
  return ok ? result("sustain", "not_applicable", null, jobEvidence) : notAssessed("sustain");
112
368
  }
113
369
  const { rotation, revocation } = binding.sustain;
114
- const handoffs = current.handoffs.flatMap((handoff, index) => handoff.step === "sustain" ? [index] : []);
115
- const evidence = [
116
- ...exchangeEvidence(current, (entry) => entry.purpose.startsWith("sustain_")),
117
- ...handoffs.map((handoff) => ({ kind: "handoff", run_digest: current.run_digest, handoff })),
118
- ];
119
370
  const witnessed = upkeepWitnesses(current);
120
- const part = (kind, purpose) => {
121
- if (kind === "manual")
122
- return handoffs.some((index) => current.handoffs[index]?.kind === "workaround")
123
- ? "workaround"
124
- : "not_assessed";
125
- return (purpose === "sustain_rotation" ? witnessed.rotation : witnessed.revocation)
126
- ? "machine"
127
- : "not_assessed";
128
- };
129
- const parts = [
130
- part(rotation.kind, "sustain_rotation"),
131
- part(revocation.kind, "sustain_revocation"),
132
- ];
133
- if (parts.includes("not_assessed"))
134
- return notAssessed("sustain");
135
- return parts.includes("workaround")
136
- ? result("sustain", "workaround", "recurring", evidence)
137
- : result("sustain", "machine", null, evidence);
371
+ // Only an exchange witnesses upkeep: a manual or unwitnessed part leaves Sustain to the probes.
372
+ const machine = (rotation.kind === "refresh" || rotation.kind === "mint") &&
373
+ witnessed.rotation &&
374
+ revocation.kind === "call" &&
375
+ witnessed.revocation;
376
+ return machine
377
+ ? result("sustain", "machine", null, exchangeEvidence(current, (entry) => entry.purpose.startsWith("sustain_")))
378
+ : notAssessed("sustain");
138
379
  })();
380
+ const pending = (found) => found?.latest === true && !reproduced(found.runs, rule);
381
+ return {
382
+ // Discover and Onboard are the probes' alone: a verified run reads no descriptor and registers nothing.
383
+ steps: [
384
+ notAssessed("discover"),
385
+ authorityStep,
386
+ notAssessed("onboard"),
387
+ payStep,
388
+ confirmStep,
389
+ sustainStep,
390
+ ],
391
+ job: jobOutcome,
392
+ reproducible: pending(jobRefusals) ||
393
+ pending(payRefusals) ||
394
+ pending(authorityRefusals) ||
395
+ (failing && !reproduced(failures, rule)),
396
+ // A verified run reads no descriptor.
397
+ discoveryRun: null,
398
+ verifiedRun: current,
399
+ standingRuns: [],
400
+ };
401
+ }
402
+ /**
403
+ * Verified outcomes replace probed ones for the steps the verified run
404
+ * assessed, and for the job (north star §5.6, precedence); a step it left
405
+ * unassessed keeps its probed outcome.
406
+ */
407
+ export function combinePaths(probed, verified) {
139
408
  return {
140
- runDigest: current.run_digest,
141
- steps: [discoverStep, delegationStep, payStep, jobStep, confirmStep, sustainStep].map((step) => step.outcome === "not_assessed" || step.outcome === "blocked"
142
- ? step
143
- : (human(current, step.step, step.evidence, step) ?? step)),
409
+ steps: probed.steps.map((step, index) => {
410
+ const replacement = verified.steps[index];
411
+ if (replacement?.step !== step.step)
412
+ throw new Error("Paths list steps in path order.");
413
+ return replacement.outcome === "not_assessed" ? step : replacement;
414
+ }),
415
+ job: verified.job.outcome === "not_assessed" ? probed.job : verified.job,
416
+ reproducible: probed.reproducible || verified.reproducible,
417
+ discoveryRun: probed.discoveryRun,
418
+ verifiedRun: verified.verifiedRun,
419
+ standingRuns: probed.standingRuns,
144
420
  };
145
421
  }
422
+ /**
423
+ * Discover (north star §5.6): `machine` when the agent finds the interface
424
+ * from the vendor's own domains and a machine-readable description covers the
425
+ * job. Found: a descriptor fetched on an Entity domain names the job's
426
+ * endpoint, or a document written for agents names its origin (the route).
427
+ * Covered: the named spec covers the job, the MCP server lists the job's tool
428
+ * (or lists tools only after authority), or that document for agents. A spec
429
+ * operation has no endpoint to name: its spec, covering the job, is the
430
+ * machine-readable interface. Otherwise a setup workaround: a person tells the
431
+ * agent where the interface is or how to call it.
432
+ */
433
+ function discoverStep(run, current, operation, target, covered, specEvidence,
434
+ /** An MCP server listed the job's tool, or lists tools only to the agent it authorizes. */
435
+ toolDescribed) {
436
+ const { attempts, facts, domains } = run.discovery;
437
+ const discovery = (attempt) => ({
438
+ kind: "discovery",
439
+ run_digest: run.run_digest,
440
+ attempt_digest: attempt,
441
+ });
442
+ if (operation.kind === "spec") {
443
+ return covered
444
+ ? result("discover", "machine", null, specEvidence)
445
+ : result("discover", "workaround", "setup", specEvidence);
446
+ }
447
+ const named = target
448
+ ? facts.filter((fact) => agentReadinessFactNamesEndpoint(fact, target) && ownDomain(fact.descriptor_url, domains))
449
+ : [];
450
+ const described = covered !== null || toolDescribed;
451
+ const routed = named.some(({ fact }) => fact === "documentation_route");
452
+ if (routed || (named.length > 0 && described)) {
453
+ return result("discover", "machine", null, [
454
+ ...named.map(({ attempt_digest }) => discovery(attempt_digest)),
455
+ ...specEvidence,
456
+ ]);
457
+ }
458
+ const evidence = [...attempts.map(discovery), ...specEvidence];
459
+ return result("discover", "workaround", "setup", evidence.length > 0 ? evidence : [{ kind: "declaration", run_digest: current.run_digest }]);
460
+ }
461
+ /** Whether a URL is on one of the Entity's domains, or a subdomain of one. */
462
+ function ownDomain(url, domains) {
463
+ const host = new URL(url).hostname.toLowerCase();
464
+ return domains.some((domain) => host === domain || host.endsWith(`.${domain}`));
465
+ }
146
466
  /** Accepted maintenance requests are not effects: correlate their readbacks. */
147
467
  function upkeepWitnesses(run) {
148
468
  const at = (purpose) => run.exchanges.findIndex((entry) => entry.purpose === purpose && entry.role === "request");
@@ -158,9 +478,6 @@ function upkeepWitnesses(run) {
158
478
  const revoke = run.exchanges[revokeAt];
159
479
  const refused = run.exchanges[refusedAt];
160
480
  const control = run.exchanges[controlAt];
161
- const succeeded = (entry) => entry?.record.outcome === "responded" &&
162
- entry.record.response.status >= 200 &&
163
- entry.record.response.status <= 299;
164
481
  const handles = (entry) => entry?.record.request.credentials.map(({ handle_digest }) => handle_digest) ?? [];
165
482
  const sameHandles = (a, b) => {
166
483
  const left = handles(a);
@@ -207,85 +524,79 @@ function upkeepWitnesses(run) {
207
524
  handles(control).every((handle) => !handles(refused).includes(handle));
208
525
  return { rotation: renewed, revocation: revoked };
209
526
  }
210
- /** A listing no binding has run: every step not assessed, so no letter. */
211
- export function unexercisedPath() {
212
- return AGENT_READINESS_STEPS.map(notAssessed);
213
- }
214
- /**
215
- * Every human step the runner recorded at `step`. A workaround dominates
216
- * approval, and recurrence is decided among handoffs of that outcome: a
217
- * recurring consent must not turn a one-time mechanical setup into recurring
218
- * work. All handoffs remain evidence, independent of their input order.
219
- */
220
- function human(run, step, evidence, inferred) {
221
- const handoffs = run.handoffs.flatMap((handoff, index) => handoff.step === step ? [{ handoff, index }] : []);
222
- if (handoffs.length === 0)
223
- return null;
224
- const kind = inferred?.outcome === "workaround" ||
225
- handoffs.some(({ handoff }) => handoff.kind === "workaround")
226
- ? "workaround"
227
- : "approval";
228
- const timing = (inferred?.outcome === kind && inferred.timing === "recurring") ||
229
- handoffs.some(({ handoff }) => handoff.kind === kind && handoff.timing === "recurring")
230
- ? "recurring"
231
- : "setup";
232
- return result(step, kind, timing, [
233
- ...evidence,
234
- ...handoffs
235
- .filter(({ index }) => !evidence.some((entry) => entry.kind === "handoff" &&
236
- entry.run_digest === run.run_digest &&
237
- entry.handoff === index))
238
- .map(({ index }) => ({ kind: "handoff", run_digest: run.run_digest, handoff: index })),
239
- ]);
240
- }
241
527
  /**
242
- * A refusal reproduced across runs: the latest run and earlier ones refused at
243
- * `purpose` with the same status from the policy's list, at least the policy's
244
- * interval apart, each time holding authority the same run proved valid (the
245
- * same credentials succeeded on another exchange) or needing none.
528
+ * Each run's refusal at `purpose` with one of `statuses`, holding authority
529
+ * the same run proved valid: at least one credential, each issued in the run
530
+ * by an exchange that succeeded or carried by another exchange that succeeded.
531
+ * A request with no credential proves nothing about authority, so it is never
532
+ * a refusal (missing authority is residue). Null when no run refused.
246
533
  */
247
- function reproducedRefusal(runs, purpose, rule) {
248
- const refusals = runs.map((run) => {
534
+ function refusals(runs, purpose, statuses) {
535
+ const found = runs.flatMap((run) => {
249
536
  const refused = run.exchanges.find((entry) => entry.purpose === purpose &&
250
537
  entry.record.outcome === "responded" &&
251
- rule.statuses.includes(entry.record.response.status));
538
+ statuses.includes(entry.record.response.status));
252
539
  if (refused?.record.outcome !== "responded")
253
- return null;
540
+ return [];
254
541
  const credentials = refused.record.request.credentials.map(({ handle_digest }) => handle_digest);
255
- const succeeded = (entry) => entry.record.outcome === "responded" &&
256
- entry.record.response.status >= 200 &&
257
- entry.record.response.status <= 299;
258
- // Authority is proved valid by the run itself: the service issued it in this
259
- // run, or it succeeded on another exchange of this run.
260
542
  const proved = (handle) => run.credentials.some(({ handle_digest, source }) => handle_digest === handle &&
261
543
  source.kind === "issued" &&
262
544
  run.exchanges.some((entry) => entry.record.exchange_digest === source.exchange_digest && succeeded(entry))) ||
263
545
  run.exchanges.some((entry) => entry !== refused &&
264
546
  succeeded(entry) &&
265
547
  entry.record.request.credentials.some(({ handle_digest }) => handle_digest === handle));
266
- const valid = credentials.every(proved);
267
- return valid ? { run, entry: refused, status: refused.record.response.status } : null;
548
+ return credentials.length > 0 && credentials.every(proved)
549
+ ? [{ run, entry: refused, status: refused.record.response.status }]
550
+ : [];
268
551
  });
269
- const latest = refusals.at(-1);
270
- if (!latest)
552
+ const last = found.at(-1);
553
+ if (!last)
271
554
  return null;
272
- const same = refusals.filter((refusal) => refusal?.status === latest.status);
273
- const first = same[0];
274
- if (!first ||
275
- same.length < rule.attempts ||
276
- Date.parse(latest.run.finished_at) - Date.parse(first.run.finished_at) <
277
- rule.minimum_interval_seconds * 1_000) {
278
- return null;
279
- }
555
+ const same = found.filter(({ status }) => status === last.status);
280
556
  return {
281
- status: latest.status,
557
+ runs: same.map(({ run }) => run),
282
558
  evidence: same.slice(-MAXIMUM_EVIDENCE).map(({ run, entry }) => ({
283
559
  kind: "exchange",
284
560
  run_digest: run.run_digest,
285
561
  exchange_digest: entry.record.exchange_digest,
286
562
  })),
563
+ latest: last.run === runs.at(-1),
287
564
  };
288
565
  }
566
+ /**
567
+ * The latest of `found` that reproduce a finding, the fewest that do, or none:
568
+ * a revision cites only these (north star §5.4).
569
+ */
570
+ function reproducing(found, rule) {
571
+ for (let start = found.length - rule.attempts; start >= 0; start -= 1) {
572
+ const stretch = found.slice(start);
573
+ if (reproduced(stretch, rule))
574
+ return stretch;
575
+ }
576
+ return [];
577
+ }
578
+ /**
579
+ * A finding reproduced as the policy asks: in the latest run and in earlier
580
+ * ones, at least `attempts` runs whose first and last are the interval apart.
581
+ */
582
+ function reproduced(found, rule) {
583
+ const first = found[0];
584
+ const last = found.at(-1);
585
+ return (first !== undefined &&
586
+ last !== undefined &&
587
+ found.length >= rule.attempts &&
588
+ Date.parse(last.finished_at) - Date.parse(first.finished_at) >=
589
+ rule.minimum_interval_seconds * 1_000);
590
+ }
591
+ function succeeded(entry) {
592
+ return (entry?.record.outcome === "responded" &&
593
+ entry.record.response.status >= 200 &&
594
+ entry.record.response.status <= 299);
595
+ }
596
+ function oldestFirst(runs) {
597
+ return [...runs].sort((left, right) => compareInstants(left.finished_at, right.finished_at) ||
598
+ (left.run_digest < right.run_digest ? -1 : 1));
599
+ }
289
600
  function exchangeEvidence(run, include) {
290
601
  return run.exchanges.filter(include).map((entry) => ({
291
602
  kind: "exchange",
@@ -293,16 +604,19 @@ function exchangeEvidence(run, include) {
293
604
  exchange_digest: entry.record.exchange_digest,
294
605
  }));
295
606
  }
296
- /** A descriptor names an endpoint when it gives its exact URI or a base the URI sits under. */
297
- function namesEndpoint(value, uri) {
298
- const base = value.replace(/\/+$/u, "");
299
- const endpoint = uri.replace(/\/+$/u, "");
300
- return endpoint === base || endpoint.startsWith(`${base}/`);
301
- }
302
607
  function result(step, outcome, timing, evidence) {
303
608
  return { step, outcome, timing, evidence: evidence.slice(0, MAXIMUM_EVIDENCE) };
304
609
  }
305
- function notAssessed(step) {
610
+ export function notAssessed(step) {
306
611
  return { step, outcome: "not_assessed", timing: null, evidence: [] };
307
612
  }
613
+ /** The job not yet settled by any run. */
614
+ export const NOT_ASSESSED_JOB = { outcome: "not_assessed", evidence: [] };
615
+ function jobResult(outcome, evidence) {
616
+ return { outcome, evidence: evidence.slice(0, MAXIMUM_EVIDENCE) };
617
+ }
618
+ /** The job unavailable on the run's declaration revision alone: it names no interface for the job. */
619
+ export function declaredUnavailable(run) {
620
+ return jobResult("unavailable", [{ kind: "declaration", run_digest: run.run_digest }]);
621
+ }
308
622
  //# sourceMappingURL=path.js.map