Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/loopover-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -21052,7 +21052,7 @@
"summary": "Verify a window of the hash-chained decision ledger (resumable via afterSeq)",
"responses": {
"200": {
"description": "Window verified clean; nextAfterSeq is the resume cursor (null at the tip). Every response also carries tipSeq/tipHash/totalCount for third-party checkpointing, contentMismatches (#9850 -- the COUNT of rows whose preimage no longer matches the digest the chain committed to; a content mismatch no longer aborts the scan, because one unreconcilable row used to hide every row after it, so a structural break further along was unreachable. The verdict is unchanged: any mismatch is still ok:false and the first is still reported as `break`), waivedContentMismatches + contentWaiver (#9850 -- a DECLARED, seq-bounded exclusion from the content re-check for rows whose preimage is unrecoverable, set per-deployment via LOOPOVER_LEDGER_CONTENT_WAIVER. Both bounds and a reason are mandatory, the range and count are always published even on a clean chain, waived mismatches are counted separately and never folded into contentMismatches, and CHAIN checks are never waived -- a waived row that is mis-chained still fails), and prunedRecords — the count of rows whose record preimage was legitimately pruned by the published retention window (chain checks still hold for them; only the content re-check is impossible, and the committed digest stays published)."
"description": "Window verified clean; nextAfterSeq is the resume cursor (null at the tip). Every response also carries tipSeq/tipHash/totalCount for third-party checkpointing, contentMismatches (#9850 -- the COUNT of rows whose preimage no longer matches the digest the chain committed to; a content mismatch no longer aborts the scan, because one unreconcilable row used to hide every row after it, so a structural break further along was unreachable. The verdict is unchanged: any mismatch is still ok:false and the first is still reported as `break`), waivedContentMismatches + contentWaiver (#9850 -- a DECLARED, seq-bounded exclusion from the content re-check for rows whose preimage is unrecoverable, set per-deployment via LOOPOVER_LEDGER_CONTENT_WAIVER. Both bounds and a reason are mandatory, the range and count are always published even on a clean chain, waived mismatches are counted separately and never folded into contentMismatches, and CHAIN checks are never waived -- a waived row that is mis-chained still fails), waivedUnchainedRecords + unchainedWaiver (#9933 -- the sibling declaration for records no ledger row ever vouched for, which the CONTENT waiver deliberately cannot cover because chain findings are never waivable. Bounded by two ABSOLUTE instants plus a declared maximum count, so the window cannot drift and one more orphan inside it than declared makes the whole waiver stop applying; a truncated TAIL stays unwaivable), and prunedRecords — the count of rows whose record preimage was legitimately pruned by the published retention window (chain checks still hold for them; only the content re-check is impossible, and the committed digest stays published)."
},
"409": {
"description": "First break found: sequence_gap | predecessor_mismatch | row_hash_mismatch | missing_record | content_mismatch | short_tail (a record newer than the verified tip has no chain entry — the truncated-tail signature) | unchained_record (an INTERIOR record has no chain entry — the failed-append signature). Records younger than the 5-minute append grace window are not reported: the record insert and its chain append are two writes moments apart, and a verify landing between them is not evidence of tampering."
Expand Down
5 changes: 5 additions & 0 deletions apps/loopover-ui/src/lib/selfhost-env-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ export const SELFHOST_ENV_REFERENCE_ROWS: SelfHostEnvReferenceRow[] = [
name: "LOOPOVER_LEDGER_CONTENT_WAIVER",
firstReference: "src/selfhost/preflight.ts",
},
{
name: "LOOPOVER_LEDGER_UNCHAINED_WAIVER",
firstReference: "src/selfhost/preflight.ts",
},
{
name: "LOOPOVER_MCP_TOKEN",
firstReference: "src/selfhost/preflight.ts",
Expand Down Expand Up @@ -789,6 +793,7 @@ export const SELFHOST_ENV_REFERENCE_MARKDOWN = [
"| `LOOPOVER_LEDGER_ANCHOR_KEYS` | `src/selfhost/preflight.ts` |",
"| `LOOPOVER_LEDGER_ANCHOR_PRIVATE_KEY` | `src/selfhost/preflight.ts` |",
"| `LOOPOVER_LEDGER_CONTENT_WAIVER` | `src/selfhost/preflight.ts` |",
"| `LOOPOVER_LEDGER_UNCHAINED_WAIVER` | `src/selfhost/preflight.ts` |",
"| `LOOPOVER_MCP_TOKEN` | `src/selfhost/preflight.ts` |",
"| `LOOPOVER_METRICS_REPO_LABELS` | `src/server.ts` |",
"| `LOOPOVER_PUBLIC_SCORE_TERMS_ALLOWED_REPOS` | `src/selfhost/inert-config.ts` |",
Expand Down
8 changes: 8 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,14 @@ declare global {
* where the alternative is a permanently-failing public endpoint. Chain checks are never waived, both
* bounds and a reason are mandatory, and the range plus its count are published by /verify. */
LOOPOVER_LEDGER_CONTENT_WAIVER?: string;
/** #9933: the sibling declaration for UNCHAINED records — decision_records rows no ledger row ever
* vouched for (the failed-append signature), which the content waiver deliberately cannot cover because
* chain findings are never waivable. Format `<fromIso>..<toIso>|<maxRecords>|<reason>`: both bounds are
* ABSOLUTE instants (so the window cannot drift the way a relative one would), and the declared count is
* a fail-closed guard — one more orphan inside the window than declared and the whole waiver stops
* applying, so a new failed append can never hide behind an old declaration. A truncated TAIL stays
* unwaivable. See parseLedgerUnchainedWaiver for the full reasoning. */
LOOPOVER_LEDGER_UNCHAINED_WAIVER?: string;
LOOPOVER_LEDGER_ANCHOR_KEYS?: string;
/** External ledger anchoring (#9270, epic #9267): the PRIVATE half of the anchor-signing keypair — an
* ECDSA P-256 key in PKCS8 PEM (the `\n`-escaped single-line form is accepted, since that is how a key
Expand Down
2 changes: 1 addition & 1 deletion src/openapi/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1882,7 +1882,7 @@ export function buildOpenApiSpec() {
tags: ["Public"],
summary: "Verify a window of the hash-chained decision ledger (resumable via afterSeq)",
responses: {
200: { description: "Window verified clean; nextAfterSeq is the resume cursor (null at the tip). Every response also carries tipSeq/tipHash/totalCount for third-party checkpointing, contentMismatches (#9850 -- the COUNT of rows whose preimage no longer matches the digest the chain committed to; a content mismatch no longer aborts the scan, because one unreconcilable row used to hide every row after it, so a structural break further along was unreachable. The verdict is unchanged: any mismatch is still ok:false and the first is still reported as `break`), waivedContentMismatches + contentWaiver (#9850 -- a DECLARED, seq-bounded exclusion from the content re-check for rows whose preimage is unrecoverable, set per-deployment via LOOPOVER_LEDGER_CONTENT_WAIVER. Both bounds and a reason are mandatory, the range and count are always published even on a clean chain, waived mismatches are counted separately and never folded into contentMismatches, and CHAIN checks are never waived -- a waived row that is mis-chained still fails), and prunedRecords — the count of rows whose record preimage was legitimately pruned by the published retention window (chain checks still hold for them; only the content re-check is impossible, and the committed digest stays published)." },
200: { description: "Window verified clean; nextAfterSeq is the resume cursor (null at the tip). Every response also carries tipSeq/tipHash/totalCount for third-party checkpointing, contentMismatches (#9850 -- the COUNT of rows whose preimage no longer matches the digest the chain committed to; a content mismatch no longer aborts the scan, because one unreconcilable row used to hide every row after it, so a structural break further along was unreachable. The verdict is unchanged: any mismatch is still ok:false and the first is still reported as `break`), waivedContentMismatches + contentWaiver (#9850 -- a DECLARED, seq-bounded exclusion from the content re-check for rows whose preimage is unrecoverable, set per-deployment via LOOPOVER_LEDGER_CONTENT_WAIVER. Both bounds and a reason are mandatory, the range and count are always published even on a clean chain, waived mismatches are counted separately and never folded into contentMismatches, and CHAIN checks are never waived -- a waived row that is mis-chained still fails), waivedUnchainedRecords + unchainedWaiver (#9933 -- the sibling declaration for records no ledger row ever vouched for, which the CONTENT waiver deliberately cannot cover because chain findings are never waivable. Bounded by two ABSOLUTE instants plus a declared maximum count, so the window cannot drift and one more orphan inside it than declared makes the whole waiver stop applying; a truncated TAIL stays unwaivable), and prunedRecords — the count of rows whose record preimage was legitimately pruned by the published retention window (chain checks still hold for them; only the content re-check is impossible, and the committed digest stays published)." },
409: { description: "First break found: sequence_gap | predecessor_mismatch | row_hash_mismatch | missing_record | content_mismatch | short_tail (a record newer than the verified tip has no chain entry — the truncated-tail signature) | unchained_record (an INTERIOR record has no chain entry — the failed-append signature). Records younger than the 5-minute append grace window are not reported: the record insert and its chain append are two writes moments apart, and a verify landing between them is not evidence of tampering." },
},
});
Expand Down
Loading
Loading