@crawlcheck/sdk 1.9.0 → 1.9.2

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 CHANGED
@@ -18,6 +18,8 @@ const { proceed, receipt } = await cc.guard("example.com", "read", { agent: "MyA
18
18
 
19
19
  Every verification returns `integrity_valid`, `issuer_trusted` and `accepted`. Act on `accepted`: it needs the signing key to be one CrawlCheck publishes, so a document someone signed with their own key is `verified` offline but never `accepted`. The client methods (`verifyDocument`, `verifyOffline`, `verifyReceiptOffline`, `guard`) check the published key directory for you.
20
20
 
21
+ Since 1.9.1 a key the rotation record lists as retired still counts for its own signing window when the expected purpose is worked out: after a decision-key rotation, receipts the old decision key signed inside its window verify, and no other key may sign a decision made while that key was live (key-policy suite cases retired-decision-key-*).
22
+
21
23
  Since 1.9.0 `logCheck` reads the chain of signed heads 200 per request (`/api/v1/log/sths`) instead of one per batch, so a first check on a client holding no heads takes one or two requests; an older server is still walked one head per request.
22
24
 
23
25
  Since 1.8.0 an MCP connect decision names the server and, with a lockfile, its approved `tools_sha256` (`preflight(domain, "connect", { mcp: { url, tools_sha256 } })`; `withPreflight` sends it for you). CrawlCheck reads the live tools from its own network: a changed tool list comes back as `block`, and the signed receipt's `assessed` block says exactly which endpoint and tool state it covered.
@@ -1895,7 +1895,7 @@ export interface paths {
1895
1895
  path?: never;
1896
1896
  cookie?: never;
1897
1897
  };
1898
- /** The last reconstruction test: the oldest archived records' evidence paths (record, report node, decision nodes, manifest node, every verifier check) resolved through the 90-day projections and again from the permanent store alone (R2 archive + seal records), compared byte for byte after canonical serialisation. Public */
1898
+ /** The last reconstruction test: the oldest archived records' evidence paths (record, report node, decision nodes, manifest node, every verifier check) resolved through the 90-day projections and again from the permanent store alone (the sealed archive and seal records), compared byte for byte after canonical serialisation. Public */
1899
1899
  get: operations["reconstructionTest"];
1900
1900
  put?: never;
1901
1901
  post?: never;
package/dist/index.js CHANGED
@@ -608,7 +608,11 @@ function atDomain(v) { const s = String(v ?? "").trim(); try {
608
608
  catch {
609
609
  return "";
610
610
  } }
611
- function atAction(v) { return AT_ACTIONS.includes(String(v)) ? String(v) : "read"; }
611
+ // 1.9.2: an action that is GIVEN but not one of the five fails closed (it used to fall back to "read", so a crew asking about
612
+ // "pay" got a read decision). An absent action still means read.
613
+ function atAction(v) { if (v === undefined || v === null || v === "")
614
+ return "read"; return AT_ACTIONS.includes(String(v)) ? String(v) : null; }
615
+ const atBadAction = (v) => ({ proceed: false, why: ["unknown action: " + String(v) + " (use read, cite, connect, transact or administer)"] });
612
616
  export function agentTools(cc, opts = {}) {
613
617
  const max = opts.maxChars ?? 20000;
614
618
  return [
@@ -620,6 +624,8 @@ export function agentTools(cc, opts = {}) {
620
624
  } },
621
625
  execute: async (a) => {
622
626
  const domain = atDomain(a?.site), action = atAction(a?.action);
627
+ if (!action)
628
+ return atBadAction(a?.action);
623
629
  if (!domain)
624
630
  return { proceed: false, why: ["not a public domain name: " + String(a?.site ?? "")] };
625
631
  const g = await cc.guard(domain, action, opts);
@@ -657,6 +663,8 @@ export function agentTools(cc, opts = {}) {
657
663
  action: { type: "string", enum: ["read", "cite"], description: "read (default) or cite, if the text will be quoted back to someone" }
658
664
  } },
659
665
  execute: async (a) => {
666
+ if (a?.action !== undefined && a?.action !== null && a?.action !== "" && a.action !== "read" && a.action !== "cite")
667
+ return Object.assign(atBadAction(a.action), { url: String(a?.url ?? "") });
660
668
  const r = await cc.guardedFetch(String(a?.url ?? ""), Object.assign({}, opts, { action: (a?.action === "cite" ? "cite" : "read") }));
661
669
  const hops = r.hops.map((h) => ({ url: h.url, domain: h.domain, decision: h.decision, proceed: h.proceed, status: h.status ?? null }));
662
670
  if (!r.proceed || !r.response)
package/dist/verify.js CHANGED
@@ -775,7 +775,7 @@ export async function keyPolicyFrom(directory, rotation) {
775
775
  }
776
776
  for (const r of (rotation && rotation.retired) || [])
777
777
  if (r && r.thumbprint)
778
- retired[r.thumbprint] = { purpose: kpPurpose(r.purpose), stopped: kpMs(r.stopped_signing_at) ?? kpMs(r.retired_at) };
778
+ retired[r.thumbprint] = { purpose: kpPurpose(r.purpose), start: kpMs(r.crawlcheck_signs_from) ?? kpMs(r.not_before) ?? kpMs(KEY_POLICY.starts[kpPurpose(r.purpose)]), stopped: kpMs(r.stopped_signing_at) ?? kpMs(r.retired_at) }; // conf3: a retired key keeps its signing window
779
779
  for (const c of (rotation && rotation.compromised) || [])
780
780
  if (c && c.thumbprint)
781
781
  compromised[c.thumbprint] = kpMs(c.compromised_since) ?? 0;
@@ -823,6 +823,9 @@ function kpExpected(objPurpose, t, kp) {
823
823
  return p;
824
824
  if (Object.keys(kp.keys).some((kid) => kp.keys[kid].purpose === p && kp.compromised[kid] === undefined && (t === null || kp.keys[kid].start === null || kp.keys[kid].start <= t)))
825
825
  return p;
826
+ // conf3: a key retired since still covered its own signing window: a decision made while a decision key was live expects that purpose
827
+ if (t !== null && Object.keys(kp.retired || {}).some((kid) => { const r = kp.retired[kid]; return r.purpose === p && kp.compromised[kid] === undefined && (r.start === null || r.start === undefined || r.start <= t) && r.stopped !== null && t <= r.stopped; }))
828
+ return p;
826
829
  }
827
830
  return "requests";
828
831
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlcheck/sdk",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "Typed client for the CrawlCheck API (generated from its OpenAPI document) and the zero-dependency offline verifier for evidence bundles and remediation receipts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",