# Developer agent prompt — Sibyla v5.0, assignment P1-0d

Hand this to the coding agent on the **development machine**, working in
`D:\fileStorage\repos\GOTT.Sibyla`, branch `feature/document-type-settings`. It is written to be
pasted as the opening message of a fresh session.

> **P1-0d supersedes `docs/AGENT-PROMPT-v5-P1-0c.md`, which is now history.** P1-0c stopped on
> 2026-08-05 at its mandatory source gate. **The stop was correct against its own instructions and
> wrong against the project** — the reason is below and you must read it before the deliverables.
> Its findings in `docs/p1-0c-status.md` are accurate, are reused here, and are **not** re-derived.
> Do not re-run P1-0c: it will stop again, identically, for the same reason.

---

> ## Read this first — state, and where to get it
>
> **`docs/PROJECT-STATE.md` is the only source of project state. Read it before this prompt's
> context, before the backlog, and before any draft.** Everything else in the repository is a
> record of a moment: reviews, correction registers, letters, status reports, other agent prompts.
> They are accurate at their date and **none of them is current state.**
>
> - **C1–C19, A1–A7, C3, group-B scope and S1–S4 are all decided.** A *needs Luís to confirm*
>   marker means the decision is taken, applied and implementable today, reversible only if he
>   objects. A *Needs Luís* marker without "confirm" is an action in his own repository, not a
>   decision.
> - **Nothing on the .NET side is blocked on Luís Nascimento**, and nothing has been since
>   2026-08-04. Do not report a Luís item as a blocker. Do not write "pending", "unconfirmed" or
>   "awaiting Luís" into any draft.
> - **P1-0 was accepted on 2026-08-05** with a four-item carve-out. You are closing it.
>
> If any document contradicts `PROJECT-STATE.md`, that file wins, the document is stale, and you
> **report it rather than carrying it forward**.

---

## Why this assignment exists — read before the deliverables

P1-0c asked for six values, all framed as *"extract verbatim from the pinned prototype"*. Three of
them **cannot be there**, and the decisions that require them say so in their own text:

- **C11** *decides to add* a sentinel DOCEFL row `EF0000000`. A row that a v5.0 correction creates
  does not pre-exist in a prototype pinned before that correction was written.
- **C12** *decides* that gap detection stops being the ungoverned OFDGAP view and becomes a new
  governed DOCEFL rule. `docs/p1-0c-status.md` records this precisely: *"C12 decides that a new
  governed rule must be written; it does not supply its EFCode or complete row."*
- **D6** introduces `Matched — ledger reference, no entry` as, in the corrections' own words,
  **"a v5.0 addition"**. BNKMAT's eleven prototype rows are the pre-D6 vocabulary, and they are
  complete as such.

These are **authoring** tasks that were written into an **extraction** assignment. The gate fired on
a category error, not on a project blocker.

**A3 is different, and its finding is real.** At the pin, `build_bnkrec.py` assigns `BRCode`
sequentially from current row order and derives `MatchGroupID` from a per-run group sequence.
That is evidence, and it **falsifies A3's premise** that a verbatim permanent natural key exists to
be extracted the way D2's was. A3 is resolved below — by measurement, not by extraction and not by
invention.

### The rule that replaces the blanket gate

The gate exists so a plausible invented value cannot be mistaken for a real one six weeks later.
**Per-field provenance achieves that purpose. Stopping the whole assignment does not.**

From here on, every field of every seed row carries exactly one marker:

| Marker | Meaning |
|---|---|
| `extracted` | copied verbatim from the pinned prototype, with source file and blob SHA recorded |
| `authored` | written as a v5.0 decision, with the deciding document and clause named |
| `absent` | no defensible value exists; the field is left blank and the reason is recorded |

**`absent` is a legitimate, expected result and is never filled in to make a row look complete.**
A field that is neither decided by C11/C12/D6 nor structurally implied by the row's own definition
is `absent`. **Do not reason a value out from neighbouring rows, from counts, or from the
vocabulary annex's marginal domains** — that inference is exactly what the original gate forbids,
and it stays forbidden. The change is that an authored row with three `absent` fields now lands and
is auditable, instead of blocking four other deliverables that never depended on it.

`docs/p1-0-vocabularies.md` remains the normative source for every *literal that already exists*.
A literal you author is new, is marked `authored`, and is a finding at the next annex regeneration
by design.

---

## What this assignment is

**Documentation only.** No C#, no EF Core migrations, no SQL, no `src/`, no `tests/`, no
`migrations/`, no database of any kind. P1-1a is writing the migration train in parallel; you do not
touch its work and it does not touch yours.

Six deliverables. **The order is deliberate: deliverables 1–3 have no dependency whatsoever on the
seed rows, and they are what P1-0c left undone for no reason. Do them first, and do not let a
difficulty in 4, 5 or 6 prevent them from landing.**

---

### 1 · Fold the standalone decisions into the drafts

A decision that sits *beside* the document it governs leaves that document asserting something false
to anyone who reads it alone.

- **`docs/p1-0-a-group-decisions.md`** — A1–A7, C3's immutability, group-B scope. In particular:
  `CounterpartyCodeName`; stored `char(1)` `FlowType`; generated `NormalizedDocumentID` with
  `DocumentIdNormalizationVersion` **inside** the unique index; `ProposedMatchBTCode`;
  `ImportBatch`'s identity PK; `FailureCategory` deleted, `Category` kept.
- **`docs/p1-0-signoff.md`** — S1 the `identifierType` vocabulary (`VAT`, `NIF`, `EORI`,
  `PassportNumber`, `NationalID`, `Other`), S2 the DOCFLG `ItemClass` snapshot, S3 the Legal archive
  sub-path `{Company}/Legal/{Subtype}/{Counterparty}/`, S4's three defaults.

**These are decided. You are transcribing, not deciding.** Two in particular were decided on
2026-08-05 and older text still shows them as open — do not "surface them as open items":

- **C3 is closed: `EnforcementStartsAt` is immutable after import or first use**, with a named
  reject trigger, no recompute path and no recompute audit table.
- **Freeze decision 13 is closed: the hash-index swap is P1-1b**, as its own reviewed migration,
  never folded into the initial train.

### 2 · Provenance markers on every column (A2)

Every column in `docs/p1-0-schema-mapping.md` gets exactly one of `source`, `derived`, `production`.
No unmarked column. This is what tells an importer what to read; its absence is how `LineNumber` and
the ENTITM inversion got through. The known starting list is in the group-A decisions file, A2.

> This is the column-level marker set from A2. It is a different axis from the per-field
> `extracted`/`authored`/`absent` provenance introduced above, which applies to *seed row values*.
> Do not merge the two vocabularies.

### 3 · Governance the drafts still do not specify (B2, B3, B4, B12)

Specify, in the discard/purge lifecycle and the roles policy:

- **B2** — Discard authority is unbounded today: any reviewer can discard a `Posted` fiscal record,
  removing it from every operational, export and archive selection, with no counter-signature and no
  reversal path.
- **B3** — *"restore for review"* names no target state, and the disposition enum is closed.
- **B4** — no entry transition into any disposition state is defined, so a freshly captured
  document, a malformed-response capture and a valid `NOT_A_DOCUMENT` are all in no legal state.
- **B12** — the counterparty is named three ways across paths and filename patterns
  (`{CounterpartyCode}`, `{Counterparty}`, `{Entity}`), none mapped to a defined column.

These block go-live, not the migration train. Specify them; implement nothing.

**B1 is now decided and you fold it in.** `docs/p1-0-b1-decision.md`, decided 2026-08-05, closes it
and **supersedes the reference-counted default** in `docs/p1-0-a-group-decisions.md` §B1 — that
default was rejected, not confirmed. Transcribe the decision into
`docs/p1-0-discard-purge-lifecycle.md`; you are not re-deciding it:

- **§2.4** — restate deletion as *this capture's own byte objects*, and name the storage invariant
  in the same sentence, so the deletion unit and the storage unit can never drift apart again.
- **§6** — state explicitly that `RetainedContentHash` is a duplicate-detection index and **never**
  a storage pointer, and that storage asset identity is per capture.
- **§1** — keep the `Purged` invariant as written; it is now true. Add one sentence: purge is
  capture-scoped storage hygiene and is **not** content erasure.
- **§3** — unchanged. Note that it is now the *second* barrier: safety rests on the storage
  invariant, not on the eligibility predicate remembering to cover the duplicate case.
- **§7** — add the acceptance case from the decision file: two byte-identical captures, one
  `Discarded` and one `Posted`; purge of the discarded one is denied by §3, **and with the
  eligibility check disabled in the harness the posted capture's bytes are still intact**, because
  they were never the same bytes.

Nothing in this repository may name, document, log or present purge as erasure, deletion of
personal data, or a right-to-erasure remedy. Content-level erasure is a named future capability,
out of P1-1a scope, not implemented — say so where it would otherwise be assumed.

---

### 4 · Land the rows that were already found at the pin

`docs/p1-0c-status.md` records these as **found and verified at
`9359c67c4ef0101218d7e0ffff1986114ba5cc7a`**. Re-verify them at the same pin — cheaply, they are
already located — then write them into the drafts, every field marked `extracted` with its source
file and blob SHA:

| Row set | Source | Closes |
|---|---|---|
| The 19 DOCTYP tuples, six source fields each | `Editor/Data/document_type_rules.json` @ `7e687f6f` | **C9** |
| `Banks / Financing` → `CL000005` · `Revenue / Intercompany` → `CL000025` | `Editor/Data/itemclass_taxonomy.json` @ `6fcfcf14` | **C15** |
| The 45 existing DOCEFL rows | `Editor/Data/flag_evaluation.json` @ `02ec1ac5` | prerequisite for C11/C12 |

The prototype is **read-only at the pin**. Nothing is written to it, committed to it or pushed.
It holds real IBANs, fiscal numbers, salaries and bank statements for Gott and Itoorer: **no real
financial or personal value reaches this repository, a commit message, a log line or a fixture.**
Controlled-vocabulary and code-table rows are the sole authorized exception, and only those.

**C9 and C15 close on this deliverable alone.** They do not wait for deliverable 5.

### 5 · Author the three v5.0 rows

Create **`docs/p1-0-c20-authored-rows.md`** — a decision document, not a status report — holding the
three rows with per-field provenance. Then write them into the drafts and reference the file.

**Step 5a — extract the shapes, which is not a seed value.** From the pin, record the ordered field
list of the DOCEFL row (20 fields) and of the BNKMAT row (14 fields), plus each field's observed
domain from `docs/p1-0-vocabularies.md`. A column name is structure, not content; extracting it
trips no gate.

**Step 5b — fill each field by exactly one of three routes, and record which:**

1. **Decided** — the value is stated in C11, C12, D6 or another closed decision. Copy it verbatim
   and cite the clause. Mark `authored`.
2. **Structurally implied by the row's own definition** — e.g. a v5.0 row is `Active=Yes` because it
   is being added as active; `AppliesToTable` follows from what the rule evaluates, expanded
   one-row-per-pair per finding 7, never semicolon-joined. Write it, mark `authored`, and record the
   one-line justification **in the same table cell as the value**. A justification you cannot state
   in one line means the field is not structurally implied — it is `absent`.
3. **Neither** — mark `absent`, leave blank, record what you looked for and why nothing defensible
   exists.

**Step 5c — the three rows:**

- **C11 sentinel.** `EFCode = EF0000000`, description *"imported instance with no rule attached"*,
  `ItemClass = Annotation`, `BlockingLevel = Non-Blocking` — all four `authored` from C11 verbatim.
  Also record its **placement**: it is a reference row, it is the FK target for FL0002653,
  FL0002654, FL0002655 and FL0002661, and it exists so those four are queryable rather than silently
  special. If a DOCEFL field has no sensible value for a row that describes the absence of a rule,
  `absent` is the correct and expected answer — say so rather than inventing a neutral-looking one.
- **C12 Monthly-gap rule.** The new governed detector that replaces OFDGAP-as-a-table. C12 fixes its
  semantics — ENTMST providers with `InvoiceFrequency == "Monthly"` exactly, `"(assumed)"`
  frequencies excluded because a gap flagged against an unconfirmed assumption manufactures false
  positives — and it fixes that the detector declares its `ItemClass` before it is added. It does
  **not** fix the EFCode. **Allocate the next unused EFCode in the existing sequence, state the
  allocation rule and the sequence position you observed, and mark it `authored`.** An allocated
  code is not an invented one as long as the allocation rule is written down; a code copied from a
  neighbouring row's pattern without stating the rule is.
- **D6 BNKMAT row.** `MatchType = Matched — ledger reference, no entry`, per D6 a distinct match
  status **excluded from the reconciliation percentage** — that exclusion is a field-level fact and
  must appear in the row, not only in prose. Allocate the BTCode by the same stated rule. The 11
  prototype rows are the pre-D6 vocabulary and are not evidence against this row's existence.

**Then tick C11 and C12 in `docs/project-todo.md` only if the rows are in the drafts.** Read,
agreed and understood is not ticked.

### 6 · A3 — resolve `BRCode` and `MatchGroupID`

**Decision, taken 2026-08-05, superseding A3's "extract verbatim" instruction, which the pin
falsified:**

- **`MatchGroupID` loses its CodeLedger bucket.** It is a per-run grouping label over a recomputed
  reconciliation view, not an entity with permanent identity. `build_bnkrec.py` derives it from
  `(MatchStatus, BMCode)` for Unmatched, Internal and Matched (payout reconciliation), and from
  `(MatchStatus, MatchedRef)` for other statuses — a computation, not a contract. Record it as
  **run-scoped, explicitly non-permanent**, remove its bucket from the P1-1a ledger scope, and state
  in the schema mapping that it must never be quoted as a stable identifier or cited in a decision
  record. **Removing a bucket is a schema-mapping and ledger-scope change; note it for P1-1a in your
  status report and do not edit P1-1a's work yourself.**
- **`BRCode` keeps its bucket and gets a permanent natural key, chosen by the method C13 already
  established and this project already accepts: measure candidate keys against the pinned data and
  take the one that holds.** For each candidate tuple over the BNKREC rows at `9359c67`, report
  distinct count and collision count in the same table shape C13 used. Choose the lowest-collision
  candidate that is also semantically defensible, state both numbers, and mark the key `authored`
  with its measurement as evidence. **If no candidate reaches zero collisions, report the best with
  its residual and do not choose** — a natural key with unexplained collisions is worse than a
  named gap, and C13 kept three residual collisions visible rather than hiding them.

Issuance stays P1-1b either way. P1-1a creates the columns and the surviving bucket; it does not
implement generation.

---

### 7 · Close the loop

- Update **`docs/PROJECT-STATE.md`**: O1a closed, O1b closed or precisely reduced, O2 closed by the
  A3 decision above, O4 closed by deliverable 3. Add a changelog row. **After the rows themselves,
  this is the deliverable that matters most** — a stale state file makes the next agent repeat this
  entire problem, which is how P1-0c happened.
- Tick C9, C11, C12 and C15 in `docs/project-todo.md`, each only if its rows are actually in a
  draft.
- Write **`docs/p1-0d-status.md`**: the pin, one line per extraction and per authored row, every
  `absent` field with where you looked, the A3 measurement table, and the exact list of paths you
  changed. **The line to read first is what you marked `absent` — that list is the real result.**

---

## What you may not do

- Touch `src/`, `tests/`, `migrations/`, any database, or any P1-1 item. P1-1a owns those.
- Write, apply or plan a migration.
- Fill an `absent` field to make a row look complete, or derive any value from neighbouring rows,
  from counts, or from the annex's marginal domains.
- Allocate a code without writing down the allocation rule in the same document.
- Copy any real financial or personal value beyond the controlled vocabulary rows named above.
- Modify, commit to, or push anything in the prototype repository.
- Reopen a decided item. C1–C19, A1–A7, C3, D1–D9, group-B scope and S1–S4 are decisions.
- Write "pending", "unconfirmed" or "awaiting Luís" anywhere.
- Merge to `main`, or claim P1-0 is fully closed unless every carve-out row is in a draft.
- **Stop the whole assignment because one field is `absent`.** That is what P1-0c did, and it cost
  four deliverables that never depended on the field.

## When to stop and report — scoped to the deliverable, never global

Stop **the affected deliverable only**, finish the others, and report:

- A draft contradicts `PROJECT-STATE.md`, the corrections, or the group-A/sign-off decisions.
- Closing an item would require a decision nobody has taken **and** that is not resolvable by the
  `absent` marker.
- No BRCode candidate key reaches zero collisions.
- You find yourself about to widen the assignment into P1-1 or into implementation.

## Definition of done

- A1–A7, C3, group-B scope and S1–S4 are **in the drafts**, not only in their decision files.
- Every column in the schema mapping carries an A2 provenance marker.
- B2, B3, B4 and B12 are specified, and B1's decision is in the lifecycle draft with the §7
  acceptance case.
- The 19 DOCTYP tuples, the two ITMCLS `CLCode`s and the 45 DOCEFL rows are in the drafts, marked
  `extracted`, with source file and blob SHA. **C9 and C15 are ticked.**
- `docs/p1-0-c20-authored-rows.md` exists, holds the three v5.0 rows with a marker on every field,
  and names the allocation rule for each new code.
- A3 is resolved: `MatchGroupID` recorded as run-scoped and its bucket removed from ledger scope;
  `BRCode`'s key chosen with its collision measurement, or its residual reported.
- `PROJECT-STATE.md` reflects reality, `docs/p1-0d-status.md` exists, and no document anywhere says
  a decided item is pending.
- `dotnet build GOTT.Sibyla.slnx` still passes — documentation-only changes must not break it. If
  the sandbox cannot reach `api.nuget.org` (`NU1301`, as in P1-0c), say so and run it on the host;
  a network failure is not a build failure and is not reported as one.
