docs← Back to article

Markdown for LLMs

LDC-E8202 — Stale approval after a template edit

The source Markdown for this article. Copy it into your assistant or download it as a text file.

Download this articlePlain text ↗
# LDC-E8202 — Stale approval after a template edit

## What it means

An approval record binds a block to two hashes: the semantic hash of the
node (what is signed in meaning) and the hash of the rendered block text
(what is signed in wording). Either side can drift afterwards — the
program changes the node, or a template edit re-renders the same node
into different text. A record whose hashes no longer match the program is
stale, and the re-review queue reports it instead of counting it as a
live approval.

Re-review the block against the current program and text. A fresh record
re-binds both hashes at once and leaves the queue.

## Example

```run
{"argv": ["approvals-check", "$ROOT/ir.json", "$ROOT/approvals.json", "--pack", "$ROOT/pack.json"], "files": {"ir.json": "{\"nodes\": [{\"id\": \"urn:demo:diagnostics#Duty\", \"kind\": \"rule\", \"package\": \"urn:demo:diagnostics\"}], \"package\": {\"name\": \"demo.diagnostics\", \"namespace\": \"urn:demo:diagnostics\", \"version\": \"0.1.0\"}, \"semanticHash\": \"sha256:0000000000000000000000000000000000000000000000000000000000000000\"}", "pack.json": "{\"coveredNodeKinds\": [], \"disclaimer\": \"probe\", \"formatVersion\": \"law.verb/0.1\", \"language\": \"en\", \"name\": \"probe.pack\", \"phrases\": {\"doc.disclaimer_title\": \"Disclaimer\", \"doc.node_id\": \"Identifier {id}\", \"doc.pack_line\": \"Pack {pack} {version} {language} {semantic_hash}\", \"doc.title\": \"Package {package} {version}\", \"gap\": \"[unverbalized: {what}]\"}, \"version\": \"0.1.0\"}", "approvals.json": "[{\"at\": \"2026-01-02T03:04:05Z\", \"kind\": \"review_approval\", \"language\": \"en\", \"reviewer\": \"urn:person:reviewer\", \"schemaVersion\": \"law.review/0.1\", \"status\": \"approved\", \"subjectId\": \"urn:demo:diagnostics#Duty\", \"subjectSemanticHash\": \"sha256:0000000000000000000000000000000000000000000000000000000000000000\", \"templatePack\": \"probe.pack@0.1.0\", \"verbalizationHash\": \"sha256:0000000000000000000000000000000000000000000000000000000000000000\"}]"}, "expect": "LDC-E8202"}
```

## Fix

```run
{"argv": ["approvals-check", "$ROOT/ir.json", "$ROOT/approvals.json", "--pack", "$ROOT/pack.json"], "files": {"ir.json": "{\"nodes\": [{\"id\": \"urn:demo:diagnostics#Duty\", \"kind\": \"rule\", \"package\": \"urn:demo:diagnostics\"}], \"package\": {\"name\": \"demo.diagnostics\", \"namespace\": \"urn:demo:diagnostics\", \"version\": \"0.1.0\"}, \"semanticHash\": \"sha256:0000000000000000000000000000000000000000000000000000000000000000\"}", "pack.json": "{\"coveredNodeKinds\": [], \"disclaimer\": \"probe\", \"formatVersion\": \"law.verb/0.1\", \"language\": \"en\", \"name\": \"probe.pack\", \"phrases\": {\"doc.disclaimer_title\": \"Disclaimer\", \"doc.node_id\": \"Identifier {id}\", \"doc.pack_line\": \"Pack {pack} {version} {language} {semantic_hash}\", \"doc.title\": \"Package {package} {version}\", \"gap\": \"[unverbalized: {what}]\"}, \"version\": \"0.1.0\"}", "approvals.json": "[{\"at\": \"2026-01-02T03:04:05Z\", \"kind\": \"review_approval\", \"language\": \"en\", \"reviewer\": \"urn:person:reviewer\", \"schemaVersion\": \"law.review/0.1\", \"status\": \"approved\", \"subjectId\": \"urn:demo:diagnostics#Duty\", \"subjectSemanticHash\": \"sha256:74b6a940703f31562861f8b9891390a5004ee1a06cb3e1f55b9bdb841bffb29c\", \"templatePack\": \"probe.pack@0.1.0\", \"verbalizationHash\": \"sha256:6fe721e63a0219f5ff06afb3a609f1ba556e92dda1e907342a8b36d787b60d55\"}]"}, "expect": null}
```

## Engine message

The review tooling reports this in its own wording:

```text
LDC-E8202: urn:demo:diagnostics#Duty: approval by urn:person:reviewer of 2026-01-02T03:04:05Z is stale: node semantics changed: a different meaning is signed
```

## Related

- [LDC-E8201](/diagnostics/LDC-E8201/) — the neighbouring review refusal.