dunstan 0.1.7 → 0.1.9
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/README.md +1 -1
- package/dist/dunstan.mjs +293 -114
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ report a pull request's handback before it says done ([`plugins/dunstan/`](plugi
|
|
|
27
27
|
/plugin install dunstan@barglabs
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
The plugin runs the MCP server with `npx -y dunstan@0.1.
|
|
30
|
+
The plugin runs the MCP server with `npx -y dunstan@0.1.9 mcp`, the `dunstan` npm package at that
|
|
31
31
|
exact version: one self-contained file, no dependencies.
|
|
32
32
|
|
|
33
33
|
**The token.** When you enable the plugin, Claude Code asks for an optional GitHub token and keeps it
|
package/dist/dunstan.mjs
CHANGED
|
@@ -8030,7 +8030,7 @@ import { readFileSync as readFileSync7 } from "node:fs";
|
|
|
8030
8030
|
import { createHash } from "node:crypto";
|
|
8031
8031
|
|
|
8032
8032
|
// src/spec/constants.ts
|
|
8033
|
-
var SPEC_VERSION = "0.1.
|
|
8033
|
+
var SPEC_VERSION = "0.1.5";
|
|
8034
8034
|
var BLOCK_VERSION = "0.1";
|
|
8035
8035
|
var SUPPORTED_BLOCK_VERSIONS = [BLOCK_VERSION];
|
|
8036
8036
|
var BLOCK_INFO_STRING = "dunstan-handback";
|
|
@@ -8468,6 +8468,14 @@ function closedBy(reference, field, evidence, repository, at, observed) {
|
|
|
8468
8468
|
"not_closing"
|
|
8469
8469
|
);
|
|
8470
8470
|
}
|
|
8471
|
+
function closesBlock(issue2, headCommit) {
|
|
8472
|
+
return {
|
|
8473
|
+
dunstan: BLOCK_VERSION,
|
|
8474
|
+
headCommit,
|
|
8475
|
+
filesChanged: [],
|
|
8476
|
+
references: [{ issue: issue2, relation: "closes" }]
|
|
8477
|
+
};
|
|
8478
|
+
}
|
|
8471
8479
|
function checkReferences(block, evidence, repository) {
|
|
8472
8480
|
return (block.references ?? []).map((reference, i) => {
|
|
8473
8481
|
const field = `/references/${i}`;
|
|
@@ -9460,6 +9468,7 @@ async function readCheckRuns(client, repository, commit, excludedIds) {
|
|
|
9460
9468
|
const workflow = run.checkSuiteId === void 0 ? void 0 : workflowRuns.get(run.checkSuiteId);
|
|
9461
9469
|
if (workflow === void 0) continue;
|
|
9462
9470
|
run.workflowPath = workflow.path;
|
|
9471
|
+
if (workflow.name !== void 0) run.workflowName = workflow.name;
|
|
9463
9472
|
run.workflowRunId = workflow.id;
|
|
9464
9473
|
run.runAttempt = workflow.attempt;
|
|
9465
9474
|
}
|
|
@@ -9487,6 +9496,8 @@ async function readWorkflowRunsAt(client, repository, commit) {
|
|
|
9487
9496
|
const run = { id: int(r.id), attempt: int(r.run_attempt), path: str(r.path) };
|
|
9488
9497
|
run.path = run.path.replace(/@.*$/, "");
|
|
9489
9498
|
if (run.id < 1 || run.attempt < 1 || run.path === "") throw new ShapeError();
|
|
9499
|
+
const name = strOrNull(r.name);
|
|
9500
|
+
if (name !== null && name !== "") run.name = name;
|
|
9490
9501
|
return { suite, run };
|
|
9491
9502
|
}
|
|
9492
9503
|
);
|
|
@@ -9741,7 +9752,7 @@ async function reportFromComment(client, repository, number4, login) {
|
|
|
9741
9752
|
// src/record/checker.ts
|
|
9742
9753
|
import { readFileSync } from "node:fs";
|
|
9743
9754
|
var CHECKER_NAME = "dunstan";
|
|
9744
|
-
var CHECKER_VERSION = "0.1.
|
|
9755
|
+
var CHECKER_VERSION = "0.1.9";
|
|
9745
9756
|
function checkerIdentity(artifact) {
|
|
9746
9757
|
return {
|
|
9747
9758
|
name: CHECKER_NAME,
|
|
@@ -9914,7 +9925,7 @@ var FILES = { "handback-block-0.1.schema.json": `{
|
|
|
9914
9925
|
}
|
|
9915
9926
|
}
|
|
9916
9927
|
}
|
|
9917
|
-
`, "record-0.1.schema.json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://barglabs.ai/dunstan/spec/schema/record-0.1.schema.json",\n "title": "Dunstan record, v0.1",\n "description": "An in-toto Statement v1 whose predicate records one check of a handback block against the record. See spec/claim-format.md.",\n "type": "object",\n "required": ["_type", "subject", "predicateType", "predicate"],\n "additionalProperties": false,\n "properties": {\n "_type": { "const": "https://in-toto.io/Statement/v1" },\n "subject": {\n "type": "array",\n "minItems": 1,\n "maxItems": 2,\n "prefixItems": [{ "$ref": "#/$defs/commitSubject" }, { "$ref": "#/$defs/blockSubject" }],\n "items": false\n },\n "predicateType": { "const": "https://barglabs.ai/dunstan/record/v0.1" },\n "predicate": { "$ref": "#/$defs/predicate" }\n },\n "allOf": [\n {\n "if": {\n "properties": {\n "predicate": {\n "properties": { "block": { "properties": { "status": { "const": "found" } } } }\n }\n }\n },\n "then": { "properties": { "subject": { "minItems": 2 } } },\n "else": { "properties": { "subject": { "maxItems": 1 } } }\n }\n ],\n "$defs": {\n "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },\n "commitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" },\n "repository": {\n "description": "owner/repo",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}$"\n },\n "issueRef": {\n "description": "owner/repo#N, always fully qualified in evidence.",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,9}$"\n },\n "timestamp": {\n "type": "string",\n "format": "date-time",\n "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\\\.[0-9]+)?Z$"\n },\n "sourceKind": {\n "enum": [\n "pull_request",\n "pull_request_files",\n "pull_request_commits",\n "closing_references",\n "repository",\n "issue",\n "commit",\n "compare",\n "check_runs",\n "workflow_runs",\n "artifact",\n "deployments"\n ]\n },\n "commitSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string",\n "pattern": "^git\\\\+https://github\\\\.com/[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}@[0-9a-f]{40}$"\n },\n "digest": {\n "type": "object",\n "required": ["gitCommit"],\n "additionalProperties": false,\n "properties": { "gitCommit": { "$ref": "#/$defs/commitSha" } }\n }\n }\n },\n "blockSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "const": "handback-block" },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "predicate": {\n "type": "object",\n "required": [\n "spec",\n "checker",\n "report",\n "block",\n "subject",\n "evidence",\n "claims",\n "verdict",\n "digests",\n "rerun",\n "assurance"\n ],\n "additionalProperties": false,\n "properties": {\n "spec": {\n "description": "The spec version the checker implements, semver.",\n "type": "string",\n "pattern": "^0\\\\.1\\\\.(?:0|[1-9][0-9]*)$"\n },\n "checker": {\n "type": "object",\n "required": ["name", "version", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "type": "string", "minLength": 1, "maxLength": 214 },\n "version": { "type": "string", "minLength": 1, "maxLength": 100 },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "report": {\n "description": "The report\'s digest and where it was read. Never its text.",\n "type": "object",\n "required": ["sha256", "source"],\n "additionalProperties": false,\n "properties": {\n "sha256": { "$ref": "#/$defs/sha256" },\n "source": {\n "type": "object",\n "required": ["kind", "locator"],\n "additionalProperties": false,\n "properties": {\n "kind": { "enum": ["pr-body", "pr-comment", "file", "stdin", "api"] },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n }\n }\n },\n "block": { "$ref": "#/$defs/block" },\n "subject": {\n "type": "object",\n "required": ["repository", "pullRequest", "headSha", "mergeSha"],\n "additionalProperties": false,\n "properties": {\n "repository": { "$ref": "#/$defs/repository" },\n "pullRequest": { "type": "integer", "minimum": 1 },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": {\n "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }]\n }\n }\n },\n "evidence": { "$ref": "#/$defs/evidence" },\n "claims": {\n "type": "array",\n "items": { "$ref": "#/$defs/claim" }\n },\n "verdict": { "$ref": "#/$defs/verdict" },\n "digests": {\n "type": "object",\n "required": ["claims", "evidence"],\n "additionalProperties": false,\n "properties": {\n "claims": { "$ref": "#/$defs/sha256" },\n "evidence": { "$ref": "#/$defs/sha256" }\n }\n },\n "rerun": {\n "type": "object",\n "required": ["offline", "online"],\n "additionalProperties": false,\n "properties": {\n "offline": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "online": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n },\n "assurance": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issuer"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "unsigned" },\n "issuer": { "const": "self-generated" }\n }\n },\n {\n "type": "object",\n "required": ["status", "issuer", "keyFingerprint"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "signed" },\n "issuer": { "type": "string", "minLength": 1, "maxLength": 320 },\n "keyFingerprint": { "type": "string", "pattern": "^SHA256:[A-Za-z0-9+/]{43}$" }\n }\n }\n ]\n }\n }\n },\n "verdict": { "enum": ["pass", "fail", "unverifiable"] },\n "blockError": {\n "type": "object",\n "required": ["code"],\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "unterminated_fence",\n "invalid_json",\n "duplicate_member",\n "unsupported_version",\n "schema_violation"\n ]\n },\n "pointer": { "type": "string", "pattern": "^(?:/.*)?$" },\n "keyword": { "type": "string", "minLength": 1 }\n }\n },\n "block": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "found" },\n "sha256": { "$ref": "#/$defs/sha256" },\n "value": { "$ref": "handback-block-0.1.schema.json" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "missing" },\n "reason": { "const": "block_missing" },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "count", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ambiguous" },\n "reason": { "const": "block_ambiguous" },\n "count": { "type": "integer", "minimum": 2 },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "errors", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "invalid" },\n "reason": { "const": "block_invalid" },\n "errors": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/blockError" } },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n }\n ]\n },\n "claim": {\n "type": "object",\n "required": ["id", "check", "field", "declared", "observed", "verdict"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "string", "minLength": 1 },\n "check": { "enum": ["head", "scope", "reference", "count", "time"] },\n "field": {\n "description": "A JSON Pointer (RFC 6901) into the block.",\n "type": "string",\n "pattern": "^/.+$"\n },\n "declared": true,\n "observed": true,\n "verdict": { "$ref": "#/$defs/verdict" },\n "reason": { "$ref": "#/$defs/claimReason" }\n },\n "if": { "properties": { "verdict": { "const": "pass" } } },\n "then": { "not": { "required": ["reason"] } },\n "else": { "required": ["reason"] }\n },\n "claimReason": {\n "type": "string",\n "pattern": "^(?:head_mismatch|declared_not_changed|undeclared_file|file_list_truncated|not_closing|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|not_found|not_reachable|count_mismatch|all_succeeded_mismatch|checks_incomplete|no_check_runs|no_comparable_record_field|record_not_found|record_ambiguous|not_merged|premature|no_deployment|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request|pull_request_files|pull_request_commits|closing_references|repository|issue|commit|compare|check_runs|workflow_runs|artifact|deployments))$"\n },\n "unread": {\n "description": "A section the checker could not use: its source errored, or the source answered without the field.",\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "source"],\n "properties": {\n "status": { "const": "unreadable" },\n "source": { "$ref": "#/$defs/sourceKind" }\n }\n },\n {\n "type": "object",\n "required": ["status", "field"],\n "properties": {\n "status": { "const": "unpopulated" },\n "field": { "type": "string", "pattern": "^[A-Za-z0-9_.]+$" }\n }\n }\n ]\n },\n "evidence": {\n "type": "object",\n "required": ["pullRequest", "sources"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "description": "Always read: a checker that cannot read the pull request writes no record.",\n "type": "object",\n "required": [\n "status",\n "number",\n "state",\n "merged",\n "mergedAt",\n "headSha",\n "mergeSha",\n "changedFiles"\n ],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "number": { "type": "integer", "minimum": 1 },\n "state": { "enum": ["open", "closed"] },\n "merged": { "type": "boolean" },\n "mergedAt": { "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }] },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": { "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }] },\n "changedFiles": { "type": "integer", "minimum": 0 }\n }\n },\n "files": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "complete", "entries"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "complete": { "type": "boolean" },\n "entries": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["path", "status"],\n "additionalProperties": false,\n "properties": {\n "path": { "type": "string", "minLength": 1 },\n "status": {\n "enum": [\n "added",\n "removed",\n "modified",\n "renamed",\n "copied",\n "changed",\n "unchanged"\n ]\n },\n "previousPath": { "type": "string", "minLength": 1 }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "closingReferences": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issues"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "issues": { "type": "array", "items": { "$ref": "#/$defs/issueRef" } }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "references": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "issue" },\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "resolvedAs": {\n "description": "Spec 0.1.3: the owner/repo#N GitHub answered the read of ref with, when it is another issue (an issue transferred to another repository).",\n "$ref": "#/$defs/issueRef"\n }\n },\n "if": { "required": ["resolvedAs"] },\n "then": { "properties": { "exists": { "const": true } } }\n },\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists", "reachableFromHead"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "ref": { "$ref": "#/$defs/commitSha" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "reachableFromHead": { "type": "boolean" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "properties": {\n "kind": { "enum": ["issue", "commit"] },\n "ref": { "type": "string", "minLength": 1 }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "closeEvents": {\n "description": "Spec 0.1.4: who closed each closes target that exists and is not among the closing references, read under the identity it resolves to; and the pull request\'s base, its repository\'s default branch and its commit ids.",\n "type": "object",\n "required": ["pullRequest", "targets"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "baseRef", "defaultBranch", "commits"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "baseRef": { "type": "string", "minLength": 1 },\n "defaultBranch": {\n "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }]\n },\n "commits": {\n "type": "array",\n "uniqueItems": true,\n "items": { "$ref": "#/$defs/commitSha" }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "targets": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["ref", "status", "type", "state", "events"],\n "additionalProperties": false,\n "properties": {\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "type": { "enum": ["issue", "pull_request"] },\n "state": { "enum": ["open", "closed", "merged"] },\n "events": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["at", "closer"],\n "additionalProperties": false,\n "properties": {\n "at": { "$ref": "#/$defs/timestamp" },\n "closer": {\n "oneOf": [\n { "type": "null" },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "pull_request" },\n "ref": { "$ref": "#/$defs/issueRef" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "oid"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "oid": { "$ref": "#/$defs/commitSha" }\n }\n },\n {\n "type": "object",\n "required": ["kind"],\n "additionalProperties": false,\n "properties": { "kind": { "const": "other" } }\n }\n ]\n }\n }\n }\n }\n },\n "if": { "properties": { "type": { "const": "issue" } } },\n "then": { "properties": { "state": { "enum": ["open", "closed"] } } }\n },\n {\n "type": "object",\n "required": ["ref"],\n "properties": { "ref": { "$ref": "#/$defs/issueRef" } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n }\n }\n },\n "checkRuns": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "commit", "excludedIds", "runs"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "commit": { "$ref": "#/$defs/commitSha" },\n "excludedIds": {\n "description": "Check runs left out of the count: the run the checker itself executes in.",\n "type": "array",\n "items": { "type": "integer", "minimum": 1 }\n },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "name", "status", "conclusion"],\n "additionalProperties": false,\n "dependentRequired": {\n "workflowPath": ["checkSuiteId", "workflowRunId", "runAttempt"],\n "workflowRunId": ["workflowPath"],\n "runAttempt": ["workflowPath"]\n },\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "name": { "type": "string" },\n "status": { "type": "string", "minLength": 1 },\n "conclusion": { "type": ["string", "null"] },\n "checkSuiteId": {\n "description": "The run\'s check suite (`check_suite.id`). A run without `workflowPath` is counted as the latest per check suite and name; a run without either is keyed by its name alone. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "integer",\n "minimum": 1\n },\n "appSlug": {\n "description": "The slug of the app that created the run (`app.slug`). Recorded, not read by any check. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "string",\n "minLength": 1\n },\n "workflowPath": {\n "description": "The `path` of the workflow run whose `check_suite_id` is the run\'s check suite, less any `@<ref>`, from the workflow runs at the head. Runs that have it are counted as the latest per workflow path and name. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "string",\n "minLength": 1\n },\n "workflowRunId": {\n "description": "The `id` of that workflow run. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n },\n "runAttempt": {\n "description": "That workflow run\'s `run_attempt` as the listing gives it, its latest attempt at the read. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "testRecords": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["record", "status", "runs"],\n "additionalProperties": false,\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" },\n "status": { "const": "ok" },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["runId", "runAttempt", "executed", "failed"],\n "additionalProperties": false,\n "properties": {\n "runId": { "type": "integer", "minimum": 1 },\n "runAttempt": { "type": "integer", "minimum": 1 },\n "executed": { "type": "integer", "minimum": 0 },\n "failed": { "type": "integer", "minimum": 0 }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["record"],\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "deployments": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["environment", "status", "deployments"],\n "additionalProperties": false,\n "properties": {\n "environment": { "type": "string", "minLength": 1 },\n "status": { "const": "ok" },\n "deployments": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "sha", "relation", "successAt"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "sha": { "$ref": "#/$defs/commitSha" },\n "relation": {\n "enum": ["head", "merge", "descendant", "unrelated", "unknown"]\n },\n "successAt": {\n "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }]\n }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["environment"],\n "properties": { "environment": { "type": "string", "minLength": 1 } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "sources": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["kind", "locator", "sha256", "readAt"],\n "additionalProperties": false,\n "properties": {\n "kind": { "$ref": "#/$defs/sourceKind" },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "sha256": { "$ref": "#/$defs/sha256" },\n "etag": { "type": "string", "minLength": 1 },\n "readAt": { "$ref": "#/$defs/timestamp" },\n "error": {\n "description": "Present when the read failed: http_<status>, network, or parse.",\n "type": "string",\n "pattern": "^(?:http_[1-5][0-9]{2}|network|parse)$"\n }\n }\n }\n }\n }\n },\n "unreadOnly": {\n "allOf": [{ "$ref": "#/$defs/unread" }],\n "unevaluatedProperties": false\n }\n }\n}\n', "record-0.2-draft.schema.json": `{
|
|
9928
|
+
`, "record-0.1.schema.json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://barglabs.ai/dunstan/spec/schema/record-0.1.schema.json",\n "title": "Dunstan record, v0.1",\n "description": "An in-toto Statement v1 whose predicate records one check of a handback block against the record. See spec/claim-format.md.",\n "type": "object",\n "required": ["_type", "subject", "predicateType", "predicate"],\n "additionalProperties": false,\n "properties": {\n "_type": { "const": "https://in-toto.io/Statement/v1" },\n "subject": {\n "type": "array",\n "minItems": 1,\n "maxItems": 2,\n "prefixItems": [{ "$ref": "#/$defs/commitSubject" }, { "$ref": "#/$defs/blockSubject" }],\n "items": false\n },\n "predicateType": { "const": "https://barglabs.ai/dunstan/record/v0.1" },\n "predicate": { "$ref": "#/$defs/predicate" }\n },\n "allOf": [\n {\n "if": {\n "properties": {\n "predicate": {\n "properties": { "block": { "properties": { "status": { "const": "found" } } } }\n }\n }\n },\n "then": { "properties": { "subject": { "minItems": 2 } } },\n "else": { "properties": { "subject": { "maxItems": 1 } } }\n }\n ],\n "$defs": {\n "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },\n "commitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" },\n "repository": {\n "description": "owner/repo",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}$"\n },\n "issueRef": {\n "description": "owner/repo#N, always fully qualified in evidence.",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,9}$"\n },\n "timestamp": {\n "type": "string",\n "format": "date-time",\n "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\\\.[0-9]+)?Z$"\n },\n "sourceKind": {\n "enum": [\n "pull_request",\n "pull_request_files",\n "pull_request_commits",\n "closing_references",\n "repository",\n "issue",\n "commit",\n "compare",\n "check_runs",\n "workflow_runs",\n "artifact",\n "deployments"\n ]\n },\n "commitSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string",\n "pattern": "^git\\\\+https://github\\\\.com/[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}@[0-9a-f]{40}$"\n },\n "digest": {\n "type": "object",\n "required": ["gitCommit"],\n "additionalProperties": false,\n "properties": { "gitCommit": { "$ref": "#/$defs/commitSha" } }\n }\n }\n },\n "blockSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "const": "handback-block" },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "predicate": {\n "type": "object",\n "required": [\n "spec",\n "checker",\n "report",\n "block",\n "subject",\n "evidence",\n "claims",\n "verdict",\n "digests",\n "rerun",\n "assurance"\n ],\n "additionalProperties": false,\n "properties": {\n "spec": {\n "description": "The spec version the checker implements, semver.",\n "type": "string",\n "pattern": "^0\\\\.1\\\\.(?:0|[1-9][0-9]*)$"\n },\n "checker": {\n "type": "object",\n "required": ["name", "version", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "type": "string", "minLength": 1, "maxLength": 214 },\n "version": { "type": "string", "minLength": 1, "maxLength": 100 },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "report": {\n "description": "The report\'s digest and where it was read. Never its text.",\n "type": "object",\n "required": ["sha256", "source"],\n "additionalProperties": false,\n "properties": {\n "sha256": { "$ref": "#/$defs/sha256" },\n "source": {\n "type": "object",\n "required": ["kind", "locator"],\n "additionalProperties": false,\n "properties": {\n "kind": { "enum": ["pr-body", "pr-comment", "file", "stdin", "api"] },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n }\n }\n },\n "block": { "$ref": "#/$defs/block" },\n "subject": {\n "type": "object",\n "required": ["repository", "pullRequest", "headSha", "mergeSha"],\n "additionalProperties": false,\n "properties": {\n "repository": { "$ref": "#/$defs/repository" },\n "pullRequest": { "type": "integer", "minimum": 1 },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": {\n "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }]\n }\n }\n },\n "evidence": { "$ref": "#/$defs/evidence" },\n "claims": {\n "type": "array",\n "items": { "$ref": "#/$defs/claim" }\n },\n "verdict": { "$ref": "#/$defs/verdict" },\n "digests": {\n "type": "object",\n "required": ["claims", "evidence"],\n "additionalProperties": false,\n "properties": {\n "claims": { "$ref": "#/$defs/sha256" },\n "evidence": { "$ref": "#/$defs/sha256" }\n }\n },\n "rerun": {\n "type": "object",\n "required": ["offline", "online"],\n "additionalProperties": false,\n "properties": {\n "offline": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "online": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n },\n "assurance": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issuer"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "unsigned" },\n "issuer": { "const": "self-generated" }\n }\n },\n {\n "type": "object",\n "required": ["status", "issuer", "keyFingerprint"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "signed" },\n "issuer": { "type": "string", "minLength": 1, "maxLength": 320 },\n "keyFingerprint": { "type": "string", "pattern": "^SHA256:[A-Za-z0-9+/]{43}$" }\n }\n }\n ]\n }\n }\n },\n "verdict": { "enum": ["pass", "fail", "unverifiable"] },\n "blockError": {\n "type": "object",\n "required": ["code"],\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "unterminated_fence",\n "invalid_json",\n "duplicate_member",\n "unsupported_version",\n "schema_violation"\n ]\n },\n "pointer": { "type": "string", "pattern": "^(?:/.*)?$" },\n "keyword": { "type": "string", "minLength": 1 }\n }\n },\n "block": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "found" },\n "sha256": { "$ref": "#/$defs/sha256" },\n "value": { "$ref": "handback-block-0.1.schema.json" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "missing" },\n "reason": { "const": "block_missing" },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "count", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ambiguous" },\n "reason": { "const": "block_ambiguous" },\n "count": { "type": "integer", "minimum": 2 },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "errors", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "invalid" },\n "reason": { "const": "block_invalid" },\n "errors": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/blockError" } },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n }\n ]\n },\n "claim": {\n "type": "object",\n "required": ["id", "check", "field", "declared", "observed", "verdict"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "string", "minLength": 1 },\n "check": { "enum": ["head", "scope", "reference", "count", "time"] },\n "field": {\n "description": "A JSON Pointer (RFC 6901) into the block.",\n "type": "string",\n "pattern": "^/.+$"\n },\n "declared": true,\n "observed": true,\n "verdict": { "$ref": "#/$defs/verdict" },\n "reason": { "$ref": "#/$defs/claimReason" }\n },\n "if": { "properties": { "verdict": { "const": "pass" } } },\n "then": { "not": { "required": ["reason"] } },\n "else": { "required": ["reason"] }\n },\n "claimReason": {\n "type": "string",\n "pattern": "^(?:head_mismatch|declared_not_changed|undeclared_file|file_list_truncated|not_closing|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|not_found|not_reachable|count_mismatch|all_succeeded_mismatch|checks_incomplete|no_check_runs|no_comparable_record_field|record_not_found|record_ambiguous|not_merged|premature|no_deployment|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request|pull_request_files|pull_request_commits|closing_references|repository|issue|commit|compare|check_runs|workflow_runs|artifact|deployments))$"\n },\n "unread": {\n "description": "A section the checker could not use: its source errored, or the source answered without the field.",\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "source"],\n "properties": {\n "status": { "const": "unreadable" },\n "source": { "$ref": "#/$defs/sourceKind" }\n }\n },\n {\n "type": "object",\n "required": ["status", "field"],\n "properties": {\n "status": { "const": "unpopulated" },\n "field": { "type": "string", "pattern": "^[A-Za-z0-9_.]+$" }\n }\n }\n ]\n },\n "evidence": {\n "type": "object",\n "required": ["pullRequest", "sources"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "description": "Always read: a checker that cannot read the pull request writes no record.",\n "type": "object",\n "required": [\n "status",\n "number",\n "state",\n "merged",\n "mergedAt",\n "headSha",\n "mergeSha",\n "changedFiles"\n ],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "number": { "type": "integer", "minimum": 1 },\n "state": { "enum": ["open", "closed"] },\n "merged": { "type": "boolean" },\n "mergedAt": { "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }] },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": { "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }] },\n "changedFiles": { "type": "integer", "minimum": 0 }\n }\n },\n "files": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "complete", "entries"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "complete": { "type": "boolean" },\n "entries": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["path", "status"],\n "additionalProperties": false,\n "properties": {\n "path": { "type": "string", "minLength": 1 },\n "status": {\n "enum": [\n "added",\n "removed",\n "modified",\n "renamed",\n "copied",\n "changed",\n "unchanged"\n ]\n },\n "previousPath": { "type": "string", "minLength": 1 }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "closingReferences": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issues"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "issues": { "type": "array", "items": { "$ref": "#/$defs/issueRef" } }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "references": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "issue" },\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "resolvedAs": {\n "description": "Spec 0.1.3: the owner/repo#N GitHub answered the read of ref with, when it is another issue (an issue transferred to another repository).",\n "$ref": "#/$defs/issueRef"\n }\n },\n "if": { "required": ["resolvedAs"] },\n "then": { "properties": { "exists": { "const": true } } }\n },\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists", "reachableFromHead"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "ref": { "$ref": "#/$defs/commitSha" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "reachableFromHead": { "type": "boolean" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "properties": {\n "kind": { "enum": ["issue", "commit"] },\n "ref": { "type": "string", "minLength": 1 }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "closeEvents": {\n "description": "Spec 0.1.4: who closed each closes target that exists and is not among the closing references, read under the identity it resolves to; and the pull request\'s base, its repository\'s default branch and its commit ids.",\n "type": "object",\n "required": ["pullRequest", "targets"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "baseRef", "defaultBranch", "commits"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "baseRef": { "type": "string", "minLength": 1 },\n "defaultBranch": {\n "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }]\n },\n "commits": {\n "type": "array",\n "uniqueItems": true,\n "items": { "$ref": "#/$defs/commitSha" }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "targets": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["ref", "status", "type", "state", "events"],\n "additionalProperties": false,\n "properties": {\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "type": { "enum": ["issue", "pull_request"] },\n "state": { "enum": ["open", "closed", "merged"] },\n "events": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["at", "closer"],\n "additionalProperties": false,\n "properties": {\n "at": { "$ref": "#/$defs/timestamp" },\n "closer": {\n "oneOf": [\n { "type": "null" },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "pull_request" },\n "ref": { "$ref": "#/$defs/issueRef" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "oid"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "oid": { "$ref": "#/$defs/commitSha" }\n }\n },\n {\n "type": "object",\n "required": ["kind"],\n "additionalProperties": false,\n "properties": { "kind": { "const": "other" } }\n }\n ]\n }\n }\n }\n }\n },\n "if": { "properties": { "type": { "const": "issue" } } },\n "then": { "properties": { "state": { "enum": ["open", "closed"] } } }\n },\n {\n "type": "object",\n "required": ["ref"],\n "properties": { "ref": { "$ref": "#/$defs/issueRef" } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n }\n }\n },\n "checkRuns": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "commit", "excludedIds", "runs"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "commit": { "$ref": "#/$defs/commitSha" },\n "excludedIds": {\n "description": "Check runs left out of the count: the run the checker itself executes in.",\n "type": "array",\n "items": { "type": "integer", "minimum": 1 }\n },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "name", "status", "conclusion"],\n "additionalProperties": false,\n "dependentRequired": {\n "workflowPath": ["checkSuiteId", "workflowRunId", "runAttempt"],\n "workflowName": ["workflowPath"],\n "workflowRunId": ["workflowPath"],\n "runAttempt": ["workflowPath"]\n },\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "name": { "type": "string" },\n "status": { "type": "string", "minLength": 1 },\n "conclusion": { "type": ["string", "null"] },\n "checkSuiteId": {\n "description": "The run\'s check suite (`check_suite.id`). A run without `workflowPath` is counted as the latest per check suite and name; a run without either is keyed by its name alone. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "integer",\n "minimum": 1\n },\n "appSlug": {\n "description": "The slug of the app that created the run (`app.slug`). Recorded, not read by any check. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "string",\n "minLength": 1\n },\n "workflowPath": {\n "description": "The `path` of the workflow run whose `check_suite_id` is the run\'s check suite, less any `@<ref>`, from the workflow runs at the head. Runs that have it are counted as the latest per workflow path and name. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "string",\n "minLength": 1\n },\n "workflowName": {\n "description": "The `name` of that workflow run, as the listing gives it; absent where the listing gives none, null or the empty string. No 0.1 check reads it. Added in spec 0.1.5.",\n "type": "string",\n "minLength": 1\n },\n "workflowRunId": {\n "description": "The `id` of that workflow run. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n },\n "runAttempt": {\n "description": "That workflow run\'s `run_attempt` as the listing gives it, its latest attempt at the read. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "testRecords": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["record", "status", "runs"],\n "additionalProperties": false,\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" },\n "status": { "const": "ok" },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["runId", "runAttempt", "executed", "failed"],\n "additionalProperties": false,\n "properties": {\n "runId": { "type": "integer", "minimum": 1 },\n "runAttempt": { "type": "integer", "minimum": 1 },\n "executed": { "type": "integer", "minimum": 0 },\n "failed": { "type": "integer", "minimum": 0 }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["record"],\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "deployments": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["environment", "status", "deployments"],\n "additionalProperties": false,\n "properties": {\n "environment": { "type": "string", "minLength": 1 },\n "status": { "const": "ok" },\n "deployments": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "sha", "relation", "successAt"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "sha": { "$ref": "#/$defs/commitSha" },\n "relation": {\n "enum": ["head", "merge", "descendant", "unrelated", "unknown"]\n },\n "successAt": {\n "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }]\n }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["environment"],\n "properties": { "environment": { "type": "string", "minLength": 1 } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "sources": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["kind", "locator", "sha256", "readAt"],\n "additionalProperties": false,\n "properties": {\n "kind": { "$ref": "#/$defs/sourceKind" },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "sha256": { "$ref": "#/$defs/sha256" },\n "etag": { "type": "string", "minLength": 1 },\n "readAt": { "$ref": "#/$defs/timestamp" },\n "error": {\n "description": "Present when the read failed: http_<status>, network, or parse.",\n "type": "string",\n "pattern": "^(?:http_[1-5][0-9]{2}|network|parse)$"\n }\n }\n }\n }\n }\n },\n "unreadOnly": {\n "allOf": [{ "$ref": "#/$defs/unread" }],\n "unevaluatedProperties": false\n }\n }\n}\n', "record-0.2-draft.schema.json": `{
|
|
9918
9929
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
9919
9930
|
"$id": "https://barglabs.ai/dunstan/spec/schema/record-0.2-draft.schema.json",
|
|
9920
9931
|
"title": "Dunstan record, DRAFT 0.2.0",
|
|
@@ -10400,7 +10411,7 @@ var FILES = { "handback-block-0.1.schema.json": `{
|
|
|
10400
10411
|
},
|
|
10401
10412
|
"readerReason": {
|
|
10402
10413
|
"type": "string",
|
|
10403
|
-
"pattern": "^(?:no_matching_record_item|declared_item_not_among_candidates|candidate_not_in_evidence|no_comparable_record_field|file_list_truncated|checks_incomplete|all_succeeded_mismatch|test_not_executed|test_outcome_mismatch|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request_files|check_runs|pull_request_commits|timeline|artifact))$"
|
|
10414
|
+
"pattern": "^(?:no_matching_record_item|declared_item_not_among_candidates|candidate_not_in_evidence|ambiguous_candidates|workflow_not_in_record|workflow_name_ambiguous|no_comparable_record_field|file_list_truncated|checks_incomplete|all_succeeded_mismatch|test_not_executed|test_outcome_mismatch|not_closing|not_found|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request_files|check_runs|pull_request_commits|closing_references|repository|issue|timeline|artifact))$"
|
|
10404
10415
|
}
|
|
10405
10416
|
}
|
|
10406
10417
|
}
|
|
@@ -11409,7 +11420,7 @@ function blockFor(kind, value, headSha) {
|
|
|
11409
11420
|
case "file_changed":
|
|
11410
11421
|
return typeof value === "string" ? { ...base, filesChanged: [value] } : void 0;
|
|
11411
11422
|
case "reference_closes":
|
|
11412
|
-
return typeof value === "string" ?
|
|
11423
|
+
return typeof value === "string" ? closesBlock(value, headSha) : void 0;
|
|
11413
11424
|
case "commit":
|
|
11414
11425
|
return typeof value === "string" && value.length === 40 ? { ...base, references: [{ commit: value, relation: "cites" }] } : void 0;
|
|
11415
11426
|
case "head_commit":
|
|
@@ -12324,6 +12335,196 @@ function extractClaims(report) {
|
|
|
12324
12335
|
return out;
|
|
12325
12336
|
}
|
|
12326
12337
|
|
|
12338
|
+
// src/retrieval/identifiers.ts
|
|
12339
|
+
var MIN_NAME_LENGTH = 4;
|
|
12340
|
+
var SHA2 = /(?<![0-9A-Za-z])[0-9a-fA-F]{7,40}(?![0-9A-Za-z])/g;
|
|
12341
|
+
var ISSUE2 = /(?<![\w/.-])((?:[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100})?#[1-9][0-9]{0,9})(?![0-9])/g;
|
|
12342
|
+
var WORD = /[A-Za-z0-9_]/;
|
|
12343
|
+
var PATH_CHAR = /[A-Za-z0-9._/-]/;
|
|
12344
|
+
var JOB_NAME_CHAR = /[A-Za-z0-9_-]/;
|
|
12345
|
+
function stringLeaves(value, out) {
|
|
12346
|
+
if (typeof value === "string") out.push(value);
|
|
12347
|
+
else if (Array.isArray(value)) for (const v of value) stringLeaves(v, out);
|
|
12348
|
+
else if (value !== null && typeof value === "object")
|
|
12349
|
+
for (const v of Object.values(value)) stringLeaves(v, out);
|
|
12350
|
+
return out;
|
|
12351
|
+
}
|
|
12352
|
+
function claimTexts(claim2) {
|
|
12353
|
+
return [claim2.text, ...stringLeaves(claim2.declaredValue, [])];
|
|
12354
|
+
}
|
|
12355
|
+
function boundedAt(haystack, needle, inside) {
|
|
12356
|
+
const out = [];
|
|
12357
|
+
if (needle.length === 0) return out;
|
|
12358
|
+
for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + 1)) {
|
|
12359
|
+
const before = haystack[at - 1];
|
|
12360
|
+
const after = haystack[at + needle.length];
|
|
12361
|
+
const next = haystack[at + needle.length + 1];
|
|
12362
|
+
const openBefore = before === void 0 || !inside.test(before);
|
|
12363
|
+
const openAfter = after === void 0 || !inside.test(after) || after === "." && (next === void 0 || !/[A-Za-z0-9]/.test(next));
|
|
12364
|
+
if (openBefore && openAfter) out.push(at);
|
|
12365
|
+
}
|
|
12366
|
+
return out;
|
|
12367
|
+
}
|
|
12368
|
+
function occursBounded(haystack, needle, inside) {
|
|
12369
|
+
return boundedAt(haystack, needle, inside).length > 0;
|
|
12370
|
+
}
|
|
12371
|
+
function classnameSpans(text, classname) {
|
|
12372
|
+
const forms = /* @__PURE__ */ new Set([classname, classname.replaceAll(".", " ")]);
|
|
12373
|
+
return [...forms].flatMap(
|
|
12374
|
+
(form) => boundedAt(text, form, WORD).map((at) => [at, at + form.length])
|
|
12375
|
+
);
|
|
12376
|
+
}
|
|
12377
|
+
function classnameNamed(text, classname, classnames) {
|
|
12378
|
+
const longer = classnames.filter((c) => c.length > classname.length).flatMap((c) => classnameSpans(text, c));
|
|
12379
|
+
return classnameSpans(text, classname).some(
|
|
12380
|
+
([from, to]) => !longer.some(([l, r]) => l <= from && to <= r)
|
|
12381
|
+
);
|
|
12382
|
+
}
|
|
12383
|
+
var CHECK_RUN_EVENTS = ["push", "pull_request", "pull_request_target", "merge_group"];
|
|
12384
|
+
var EVENT_SUFFIX = new RegExp(`^ \\((?:${CHECK_RUN_EVENTS.join("|")})\\)`);
|
|
12385
|
+
var WORKFLOW_JOB = /(\S+) \/ /g;
|
|
12386
|
+
function afterWorkflows(text) {
|
|
12387
|
+
return [...text.matchAll(WORKFLOW_JOB)].map((m) => m.index + m[0].length);
|
|
12388
|
+
}
|
|
12389
|
+
function jobNamed(text, name, longer) {
|
|
12390
|
+
if (text === name) return true;
|
|
12391
|
+
const workflowed = afterWorkflows(text);
|
|
12392
|
+
return boundedAt(text, name, JOB_NAME_CHAR).some(
|
|
12393
|
+
(at) => jobNamedAt(text, name, at, longer, workflowed.includes(at))
|
|
12394
|
+
);
|
|
12395
|
+
}
|
|
12396
|
+
function jobNamedAt(text, name, at, longer, withWorkflow) {
|
|
12397
|
+
const end = at + name.length;
|
|
12398
|
+
return (!text.startsWith(" (", end) || withWorkflow && EVENT_SUFFIX.test(text.slice(end))) && !longer.some(
|
|
12399
|
+
(l) => boundedAt(text, l, JOB_NAME_CHAR).some((from) => from <= at && end <= from + l.length)
|
|
12400
|
+
);
|
|
12401
|
+
}
|
|
12402
|
+
var longest = (names) => [...names].sort((a, b) => b.length - a.length)[0];
|
|
12403
|
+
function jobAt(text, at, jobNames) {
|
|
12404
|
+
return longest(
|
|
12405
|
+
jobNames.filter((name) => {
|
|
12406
|
+
const longer = jobNames.filter((n) => n.length > name.length && n.includes(name));
|
|
12407
|
+
return boundedAt(text, name, JOB_NAME_CHAR).includes(at) && jobNamedAt(text, name, at, longer, true);
|
|
12408
|
+
})
|
|
12409
|
+
);
|
|
12410
|
+
}
|
|
12411
|
+
function workflowBefore(text, slash, token, workflows) {
|
|
12412
|
+
const whole = workflows.filter((w) => {
|
|
12413
|
+
const from = slash - w.length;
|
|
12414
|
+
return w.length > 0 && from >= 0 && text.startsWith(w, from) && !PATH_CHAR.test(text[from - 1] ?? " ");
|
|
12415
|
+
});
|
|
12416
|
+
return longest(whole) ?? token;
|
|
12417
|
+
}
|
|
12418
|
+
function workflowJobs(text, jobNames, workflows = []) {
|
|
12419
|
+
const out = [];
|
|
12420
|
+
for (const m of text.matchAll(WORKFLOW_JOB)) {
|
|
12421
|
+
const at = m.index + m[0].length;
|
|
12422
|
+
const job = jobAt(text, at, jobNames);
|
|
12423
|
+
if (job === void 0) continue;
|
|
12424
|
+
out.push({ workflow: workflowBefore(text, at - 3, m[1], workflows), job });
|
|
12425
|
+
}
|
|
12426
|
+
return out;
|
|
12427
|
+
}
|
|
12428
|
+
var PARENTHESIS = /^ \([^()]*\)/;
|
|
12429
|
+
function unheldSuffixJobs(text, jobNames) {
|
|
12430
|
+
const out = [];
|
|
12431
|
+
for (const at of afterWorkflows(text)) {
|
|
12432
|
+
if (jobAt(text, at, jobNames) !== void 0) continue;
|
|
12433
|
+
const job = longest(
|
|
12434
|
+
jobNames.filter((name) => {
|
|
12435
|
+
const rest = text.slice(at + name.length);
|
|
12436
|
+
return boundedAt(text, name, JOB_NAME_CHAR).includes(at) && PARENTHESIS.test(rest) && !EVENT_SUFFIX.test(rest);
|
|
12437
|
+
})
|
|
12438
|
+
);
|
|
12439
|
+
if (job !== void 0) out.push(job);
|
|
12440
|
+
}
|
|
12441
|
+
return out;
|
|
12442
|
+
}
|
|
12443
|
+
function shaTokens(texts) {
|
|
12444
|
+
const out = [];
|
|
12445
|
+
for (const text of texts) {
|
|
12446
|
+
for (const m of text.matchAll(SHA2)) {
|
|
12447
|
+
const token = m[0].toLowerCase();
|
|
12448
|
+
if (token.length === 40 || /[0-9]/.test(token)) out.push(token);
|
|
12449
|
+
}
|
|
12450
|
+
}
|
|
12451
|
+
return out;
|
|
12452
|
+
}
|
|
12453
|
+
function issueRefs(texts, repository) {
|
|
12454
|
+
const out = [];
|
|
12455
|
+
for (const text of texts) {
|
|
12456
|
+
for (const m of text.matchAll(ISSUE2)) out.push(qualifyIssue(m[1], repository));
|
|
12457
|
+
}
|
|
12458
|
+
return out;
|
|
12459
|
+
}
|
|
12460
|
+
function identifierMatches(claim2, items, repository) {
|
|
12461
|
+
const texts = claimTexts(claim2);
|
|
12462
|
+
const shas = shaTokens(texts);
|
|
12463
|
+
const issues = issueRefs(texts, repository);
|
|
12464
|
+
const named = (value, inside) => texts.some((t) => t === value || occursBounded(t, value, inside));
|
|
12465
|
+
const qualified = (item) => item.type === "test" && item.id !== item.fields[0]?.[1] && named(item.id, WORD);
|
|
12466
|
+
const qualifiedNames = new Set(
|
|
12467
|
+
items.filter(qualified).map((item) => item.fields[0]?.[1])
|
|
12468
|
+
);
|
|
12469
|
+
const classnameOf = (item) => item.fields.find(([f]) => f === "classname")?.[1];
|
|
12470
|
+
const classnames = [
|
|
12471
|
+
...new Set(items.filter((item) => item.type === "test").map(classnameOf))
|
|
12472
|
+
].filter((c) => c !== void 0);
|
|
12473
|
+
const bareNamed = /* @__PURE__ */ new Map();
|
|
12474
|
+
for (const item of items) {
|
|
12475
|
+
const name = item.fields[0]?.[1];
|
|
12476
|
+
if (item.type !== "test" || name.length < MIN_NAME_LENGTH || qualifiedNames.has(name) || !named(name, WORD))
|
|
12477
|
+
continue;
|
|
12478
|
+
bareNamed.set(name, [...bareNamed.get(name) ?? [], item]);
|
|
12479
|
+
}
|
|
12480
|
+
const namedClassnames = /* @__PURE__ */ new Map();
|
|
12481
|
+
for (const [name, cases] of bareNamed) {
|
|
12482
|
+
if (cases.length < 2) continue;
|
|
12483
|
+
const said = cases.map(classnameOf).filter(
|
|
12484
|
+
(c) => c !== void 0 && c.length >= MIN_NAME_LENGTH && texts.some((t) => classnameNamed(t, c, classnames))
|
|
12485
|
+
);
|
|
12486
|
+
if (said.length > 0) namedClassnames.set(name, new Set(said));
|
|
12487
|
+
}
|
|
12488
|
+
const underNamedClassname = (item, name) => {
|
|
12489
|
+
const said = namedClassnames.get(name);
|
|
12490
|
+
const classname = classnameOf(item);
|
|
12491
|
+
return said === void 0 || classname !== void 0 && said.has(classname);
|
|
12492
|
+
};
|
|
12493
|
+
const jobNames = items.filter((item) => item.type === "check_run").map((item) => item.fields[0]?.[1]);
|
|
12494
|
+
const matches = /* @__PURE__ */ new Map();
|
|
12495
|
+
items.forEach((item, i) => {
|
|
12496
|
+
for (const [field, value] of item.fields) {
|
|
12497
|
+
let hit = false;
|
|
12498
|
+
switch (`${item.type}.${field}`) {
|
|
12499
|
+
case "commit.sha":
|
|
12500
|
+
hit = shas.some((s) => value.startsWith(s));
|
|
12501
|
+
break;
|
|
12502
|
+
case "timeline_event.ref":
|
|
12503
|
+
hit = /^[0-9a-f]{40}$/.test(value) ? shas.some((s) => value.startsWith(s)) : issues.some((ref) => sameIssue(ref, value));
|
|
12504
|
+
break;
|
|
12505
|
+
case "file.path":
|
|
12506
|
+
case "file.previousPath":
|
|
12507
|
+
hit = named(value, PATH_CHAR);
|
|
12508
|
+
break;
|
|
12509
|
+
case "check_run.name": {
|
|
12510
|
+
const longer = jobNames.filter((n) => n.length > value.length && n.includes(value));
|
|
12511
|
+
hit = value.length >= MIN_NAME_LENGTH && texts.some((t) => jobNamed(t, value, longer));
|
|
12512
|
+
break;
|
|
12513
|
+
}
|
|
12514
|
+
case "test.name":
|
|
12515
|
+
hit = value.length >= MIN_NAME_LENGTH && named(value, WORD) && (!qualifiedNames.has(value) || qualified(item)) && underNamedClassname(item, value);
|
|
12516
|
+
break;
|
|
12517
|
+
}
|
|
12518
|
+
if (hit) {
|
|
12519
|
+
matches.set(i, field);
|
|
12520
|
+
return;
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
if (qualified(item)) matches.set(i, "id");
|
|
12524
|
+
});
|
|
12525
|
+
return matches;
|
|
12526
|
+
}
|
|
12527
|
+
|
|
12327
12528
|
// src/retrieval/items.ts
|
|
12328
12529
|
function testId(test) {
|
|
12329
12530
|
return test.classname === void 0 ? test.name : `${test.classname}.${test.name}`;
|
|
@@ -12465,116 +12666,12 @@ var Bm25Index = class {
|
|
|
12465
12666
|
}
|
|
12466
12667
|
};
|
|
12467
12668
|
|
|
12468
|
-
// src/retrieval/identifiers.ts
|
|
12469
|
-
var MIN_NAME_LENGTH = 4;
|
|
12470
|
-
var SHA2 = /(?<![0-9A-Za-z])[0-9a-fA-F]{7,40}(?![0-9A-Za-z])/g;
|
|
12471
|
-
var ISSUE2 = /(?<![\w/.-])((?:[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100})?#[1-9][0-9]{0,9})(?![0-9])/g;
|
|
12472
|
-
var WORD = /[A-Za-z0-9_]/;
|
|
12473
|
-
var PATH_CHAR = /[A-Za-z0-9._/-]/;
|
|
12474
|
-
var JOB_NAME_CHAR = /[A-Za-z0-9_-]/;
|
|
12475
|
-
function stringLeaves(value, out) {
|
|
12476
|
-
if (typeof value === "string") out.push(value);
|
|
12477
|
-
else if (Array.isArray(value)) for (const v of value) stringLeaves(v, out);
|
|
12478
|
-
else if (value !== null && typeof value === "object")
|
|
12479
|
-
for (const v of Object.values(value)) stringLeaves(v, out);
|
|
12480
|
-
return out;
|
|
12481
|
-
}
|
|
12482
|
-
function claimTexts(claim2) {
|
|
12483
|
-
return [claim2.text, ...stringLeaves(claim2.declaredValue, [])];
|
|
12484
|
-
}
|
|
12485
|
-
function boundedAt(haystack, needle, inside) {
|
|
12486
|
-
const out = [];
|
|
12487
|
-
if (needle.length === 0) return out;
|
|
12488
|
-
for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + 1)) {
|
|
12489
|
-
const before = haystack[at - 1];
|
|
12490
|
-
const after = haystack[at + needle.length];
|
|
12491
|
-
const next = haystack[at + needle.length + 1];
|
|
12492
|
-
const openBefore = before === void 0 || !inside.test(before);
|
|
12493
|
-
const openAfter = after === void 0 || !inside.test(after) || after === "." && (next === void 0 || !/[A-Za-z0-9]/.test(next));
|
|
12494
|
-
if (openBefore && openAfter) out.push(at);
|
|
12495
|
-
}
|
|
12496
|
-
return out;
|
|
12497
|
-
}
|
|
12498
|
-
function occursBounded(haystack, needle, inside) {
|
|
12499
|
-
return boundedAt(haystack, needle, inside).length > 0;
|
|
12500
|
-
}
|
|
12501
|
-
function jobNamed(text, name, longer) {
|
|
12502
|
-
if (text === name) return true;
|
|
12503
|
-
return boundedAt(text, name, JOB_NAME_CHAR).some(
|
|
12504
|
-
(at) => !text.startsWith(" (", at + name.length) && !longer.some(
|
|
12505
|
-
(l) => boundedAt(text, l, JOB_NAME_CHAR).some(
|
|
12506
|
-
(from) => from <= at && at + name.length <= from + l.length
|
|
12507
|
-
)
|
|
12508
|
-
)
|
|
12509
|
-
);
|
|
12510
|
-
}
|
|
12511
|
-
function shaTokens(texts) {
|
|
12512
|
-
const out = [];
|
|
12513
|
-
for (const text of texts) {
|
|
12514
|
-
for (const m of text.matchAll(SHA2)) {
|
|
12515
|
-
const token = m[0].toLowerCase();
|
|
12516
|
-
if (token.length === 40 || /[0-9]/.test(token)) out.push(token);
|
|
12517
|
-
}
|
|
12518
|
-
}
|
|
12519
|
-
return out;
|
|
12520
|
-
}
|
|
12521
|
-
function issueRefs(texts, repository) {
|
|
12522
|
-
const out = [];
|
|
12523
|
-
for (const text of texts) {
|
|
12524
|
-
for (const m of text.matchAll(ISSUE2)) out.push(qualifyIssue(m[1], repository));
|
|
12525
|
-
}
|
|
12526
|
-
return out;
|
|
12527
|
-
}
|
|
12528
|
-
function identifierMatches(claim2, items, repository) {
|
|
12529
|
-
const texts = claimTexts(claim2);
|
|
12530
|
-
const shas = shaTokens(texts);
|
|
12531
|
-
const issues = issueRefs(texts, repository);
|
|
12532
|
-
const named = (value, inside) => texts.some((t) => t === value || occursBounded(t, value, inside));
|
|
12533
|
-
const qualified = (item) => item.type === "test" && item.id !== item.fields[0]?.[1] && named(item.id, WORD);
|
|
12534
|
-
const qualifiedNames = new Set(
|
|
12535
|
-
items.filter(qualified).map((item) => item.fields[0]?.[1])
|
|
12536
|
-
);
|
|
12537
|
-
const jobNames = items.filter((item) => item.type === "check_run").map((item) => item.fields[0]?.[1]);
|
|
12538
|
-
const matches = /* @__PURE__ */ new Map();
|
|
12539
|
-
items.forEach((item, i) => {
|
|
12540
|
-
for (const [field, value] of item.fields) {
|
|
12541
|
-
let hit = false;
|
|
12542
|
-
switch (`${item.type}.${field}`) {
|
|
12543
|
-
case "commit.sha":
|
|
12544
|
-
hit = shas.some((s) => value.startsWith(s));
|
|
12545
|
-
break;
|
|
12546
|
-
case "timeline_event.ref":
|
|
12547
|
-
hit = /^[0-9a-f]{40}$/.test(value) ? shas.some((s) => value.startsWith(s)) : issues.some((ref) => sameIssue(ref, value));
|
|
12548
|
-
break;
|
|
12549
|
-
case "file.path":
|
|
12550
|
-
case "file.previousPath":
|
|
12551
|
-
hit = named(value, PATH_CHAR);
|
|
12552
|
-
break;
|
|
12553
|
-
case "check_run.name": {
|
|
12554
|
-
const longer = jobNames.filter((n) => n.length > value.length && n.includes(value));
|
|
12555
|
-
hit = value.length >= MIN_NAME_LENGTH && texts.some((t) => jobNamed(t, value, longer));
|
|
12556
|
-
break;
|
|
12557
|
-
}
|
|
12558
|
-
case "test.name":
|
|
12559
|
-
hit = value.length >= MIN_NAME_LENGTH && named(value, WORD) && (!qualifiedNames.has(value) || qualified(item));
|
|
12560
|
-
break;
|
|
12561
|
-
}
|
|
12562
|
-
if (hit) {
|
|
12563
|
-
matches.set(i, field);
|
|
12564
|
-
return;
|
|
12565
|
-
}
|
|
12566
|
-
}
|
|
12567
|
-
if (qualified(item)) matches.set(i, "id");
|
|
12568
|
-
});
|
|
12569
|
-
return matches;
|
|
12570
|
-
}
|
|
12571
|
-
|
|
12572
12669
|
// src/retrieval/retrieve.ts
|
|
12573
12670
|
var FILL_TO_K = "to-k";
|
|
12574
12671
|
var ARM_A_FILL = "A-fill";
|
|
12575
12672
|
var BM25_FLOOR = 1;
|
|
12576
12673
|
var SCORE_DECIMALS = 6;
|
|
12577
|
-
function
|
|
12674
|
+
function roundScore(score) {
|
|
12578
12675
|
const scale = 10 ** SCORE_DECIMALS;
|
|
12579
12676
|
return Math.round(score * scale) / scale;
|
|
12580
12677
|
}
|
|
@@ -12622,7 +12719,7 @@ function retrieve(claim2, items, opts) {
|
|
|
12622
12719
|
});
|
|
12623
12720
|
return;
|
|
12624
12721
|
}
|
|
12625
|
-
const score =
|
|
12722
|
+
const score = roundScore(scores[order] ?? 0);
|
|
12626
12723
|
if (!fill && score < BM25_FLOOR) return;
|
|
12627
12724
|
ranked.push({
|
|
12628
12725
|
order,
|
|
@@ -12645,6 +12742,7 @@ var READER_CLAIM_KINDS = /* @__PURE__ */ new Map([
|
|
|
12645
12742
|
["file_changed", "file"],
|
|
12646
12743
|
["commit_present", "commit"],
|
|
12647
12744
|
["reference_in_timeline", "timeline_event"],
|
|
12745
|
+
["reference_closes", "timeline_event"],
|
|
12648
12746
|
["check_succeeded", "check_run"],
|
|
12649
12747
|
["test_passed", "test"]
|
|
12650
12748
|
]);
|
|
@@ -12698,6 +12796,66 @@ function combinedCandidates(typed, floor) {
|
|
|
12698
12796
|
const exact = typed.filter((c) => c.score === null);
|
|
12699
12797
|
return exact.length > 0 ? exact : typed.filter((c) => c.score !== null && c.score >= floor);
|
|
12700
12798
|
}
|
|
12799
|
+
function outcomeOf(type, entry) {
|
|
12800
|
+
if (type === "test") return entry.outcome;
|
|
12801
|
+
const run = entry;
|
|
12802
|
+
if (run.status !== "completed") return "not_completed";
|
|
12803
|
+
return run.conclusion === "success" ? "succeeded" : "failed";
|
|
12804
|
+
}
|
|
12805
|
+
function ambiguousCandidates(combined, outcome) {
|
|
12806
|
+
const scores = combined.map((c) => c.score === null ? null : roundScore(c.score));
|
|
12807
|
+
if (scores.length < 2 || scores.includes(null)) return false;
|
|
12808
|
+
const top = Math.max(...scores);
|
|
12809
|
+
const tied = combined.filter((_, i) => scores[i] === top);
|
|
12810
|
+
return tied.length >= 2 && new Set(tied.map(outcome)).size > 1;
|
|
12811
|
+
}
|
|
12812
|
+
function namedWorkflowRuns(text, combined, evidence) {
|
|
12813
|
+
const section = evidence.checkRuns;
|
|
12814
|
+
if (section?.status !== "ok") return [...combined];
|
|
12815
|
+
const jobNames = [...new Set(section.runs.map((r) => r.name))];
|
|
12816
|
+
if (unheldSuffixJobs(text, jobNames).length > 0) return { reason: "no_matching_record_item" };
|
|
12817
|
+
const recordedWorkflows = [
|
|
12818
|
+
...new Set(
|
|
12819
|
+
section.runs.flatMap((r) => [r.workflowPath, r.workflowName]).filter((w) => w !== void 0)
|
|
12820
|
+
)
|
|
12821
|
+
];
|
|
12822
|
+
const named = workflowJobs(text, jobNames, recordedWorkflows);
|
|
12823
|
+
if (named.length === 0) return [...combined];
|
|
12824
|
+
const counted = new Map(
|
|
12825
|
+
countedCheckRuns(section.runs, section.excludedIds).map((r) => [String(r.id), r])
|
|
12826
|
+
);
|
|
12827
|
+
const runOf = (c) => counted.get(c.id);
|
|
12828
|
+
const isOf = (r, workflow) => r.workflowPath === workflow || r.workflowName === workflow;
|
|
12829
|
+
let kept = [...combined];
|
|
12830
|
+
for (const job of new Set(named.map((n) => n.job))) {
|
|
12831
|
+
const runs = section.runs.filter((r) => r.name === job);
|
|
12832
|
+
if (runs.some((r) => r.workflowPath === void 0)) {
|
|
12833
|
+
if (runs.length >= 2) return { reason: "workflow_not_in_record" };
|
|
12834
|
+
continue;
|
|
12835
|
+
}
|
|
12836
|
+
const workflows = new Set(named.filter((n) => n.job === job).map((n) => n.workflow));
|
|
12837
|
+
for (const workflow of workflows) {
|
|
12838
|
+
const byPath = runs.some((r) => r.workflowPath === workflow);
|
|
12839
|
+
if (!byPath && runs.some((r) => r.workflowName === void 0)) {
|
|
12840
|
+
return { reason: "workflow_not_in_record" };
|
|
12841
|
+
}
|
|
12842
|
+
const paths = new Set(runs.filter((r) => isOf(r, workflow)).map((r) => r.workflowPath));
|
|
12843
|
+
if (paths.size >= 2) return { reason: "workflow_name_ambiguous" };
|
|
12844
|
+
}
|
|
12845
|
+
kept = kept.filter((c) => {
|
|
12846
|
+
const run = runOf(c);
|
|
12847
|
+
return run?.name !== job || [...workflows].some((w) => isOf(run, w));
|
|
12848
|
+
});
|
|
12849
|
+
for (const workflow of workflows) {
|
|
12850
|
+
const of = (r) => r?.name === job && isOf(r, workflow);
|
|
12851
|
+
if (kept.some((c) => of(runOf(c)))) continue;
|
|
12852
|
+
return {
|
|
12853
|
+
reason: [...counted.values()].some(of) ? "declared_item_not_among_candidates" : "workflow_not_in_record"
|
|
12854
|
+
};
|
|
12855
|
+
}
|
|
12856
|
+
}
|
|
12857
|
+
return kept;
|
|
12858
|
+
}
|
|
12701
12859
|
function checkReaderClaim(claim2, evidence, repository) {
|
|
12702
12860
|
const type = READER_CLAIM_KINDS.get(claim2.kind);
|
|
12703
12861
|
if (type === void 0) return unverifiable2("no_comparable_record_field");
|
|
@@ -12706,6 +12864,13 @@ function checkReaderClaim(claim2, evidence, repository) {
|
|
|
12706
12864
|
if (wantsBoolean ? typeof declared !== "boolean" : typeof declared !== "string") {
|
|
12707
12865
|
return unverifiable2("no_comparable_record_field");
|
|
12708
12866
|
}
|
|
12867
|
+
if (claim2.kind === "reference_closes") {
|
|
12868
|
+
if (!ISSUE_REF2.test(declared)) return unverifiable2("no_comparable_record_field");
|
|
12869
|
+
const head = evidence.pullRequest.headSha;
|
|
12870
|
+
const row = checkReferences(closesBlock(declared, head), evidence, repository)[0];
|
|
12871
|
+
if (row === void 0) return unverifiable2("no_comparable_record_field");
|
|
12872
|
+
return verdictOf(row.verdict, row.observed ?? null, row.reason);
|
|
12873
|
+
}
|
|
12709
12874
|
const missing = sectionReason(type, evidence);
|
|
12710
12875
|
if (missing !== void 0) return unverifiable2(missing);
|
|
12711
12876
|
const entries = lookup(type, evidence);
|
|
@@ -12713,12 +12878,20 @@ function checkReaderClaim(claim2, evidence, repository) {
|
|
|
12713
12878
|
if (typed.some((c) => entries.get(c.id) === void 0)) {
|
|
12714
12879
|
return unverifiable2("candidate_not_in_evidence");
|
|
12715
12880
|
}
|
|
12716
|
-
|
|
12717
|
-
const observed = relevant.map((c) => entries.get(c.id));
|
|
12881
|
+
let relevant = combinedCandidates(typed, claim2.retrieval.floor);
|
|
12718
12882
|
const truncated = type === "file" && evidence.files?.status === "ok" && !evidence.files.complete;
|
|
12719
12883
|
if (relevant.length === 0) {
|
|
12720
12884
|
return unverifiable2(truncated ? "file_list_truncated" : "no_matching_record_item");
|
|
12721
12885
|
}
|
|
12886
|
+
if (type === "check_run") {
|
|
12887
|
+
const scoped = namedWorkflowRuns(claim2.text, relevant, evidence);
|
|
12888
|
+
if (!Array.isArray(scoped)) return unverifiable2(scoped.reason);
|
|
12889
|
+
relevant = scoped;
|
|
12890
|
+
}
|
|
12891
|
+
const observed = relevant.map((c) => entries.get(c.id));
|
|
12892
|
+
if (wantsBoolean && ambiguousCandidates(relevant, (c) => outcomeOf(type, entries.get(c.id)))) {
|
|
12893
|
+
return unverifiable2("ambiguous_candidates");
|
|
12894
|
+
}
|
|
12722
12895
|
switch (type) {
|
|
12723
12896
|
case "file": {
|
|
12724
12897
|
const files = observed;
|
|
@@ -12772,7 +12945,13 @@ function readReaderClaim(input2, evidence, repository) {
|
|
|
12772
12945
|
retrieval,
|
|
12773
12946
|
candidates,
|
|
12774
12947
|
...checkReaderClaim(
|
|
12775
|
-
{
|
|
12948
|
+
{
|
|
12949
|
+
text: input2.text,
|
|
12950
|
+
kind: input2.kind,
|
|
12951
|
+
declaredValue: input2.declaredValue,
|
|
12952
|
+
candidates,
|
|
12953
|
+
retrieval
|
|
12954
|
+
},
|
|
12776
12955
|
evidence,
|
|
12777
12956
|
repository
|
|
12778
12957
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dunstan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"mcpName": "io.github.BargLabs/dunstan",
|
|
5
5
|
"description": "Holds a coding agent's completion report to the record: deterministic checks of a declared handback block against the repository's own metadata.",
|
|
6
6
|
"license": "AGPL-3.0-only",
|