# Census: carlos system box publish-record

Status: RULED as census-ruling-box-publish-record (platform-1e, R1-R4), on
ea68e8a6. This note records the ruling and the two fix rounds after it:
3165a3bf applied the ruling, and the second round replaced exact evidence
matching with enrollment's own verifier. platform-1e rules as the host
claim's owner.

Branch `box-publish-record`, cut from main at b1118925 (region HA 2b).

## The ruling

- **R1.** The verb refuses euid 0 before any store, IMDS or host read. Root
  cannot reach `publishBoxRecord` through it. The overlay header records
  "root unreachable: verb refuses euid 0 (R1)" rather than a unit row.
- **R2.** The one supported way to run it:
  `systemd-run --wait --pipe -p EnvironmentFile=/etc/carlos/host.env -p User=carlos /opt/carlos/carlos system box publish-record`.
- **R3.** No region HA gate. Run after the loop has published, the verb is a
  no-op: it keeps the loop's record and writes nothing.
- **R4.** `boxRecordStore.Put` is `nonrecord`, on condition that
  `boxRecordStore` is the only store the verb hands `publishBoxRecord`. The
  store-guard test asserts zero S3 requests for a delete, a list, an
  overwrite and any other key.

## What changed

`carlos system box publish-record` makes one `publishBoxRecord` attempt on a
box, by hand, without `boxRecordLoop`'s `CARLOS_REGION_HA` gate. The dev claim
trial needs records at `control/boxes/<box>.json` for leo-climbing
(carlos-dev-ie-b) and sophie-knitting (carlos-dev-ie). Region HA stays off on
dev.

- `internal/host/boxpublish.go` (new): `RunSystemBoxPublishRecord`,
  `boxPublishRun`, `heldEvidence`, `boxRecordStore` and the verb's lines.
- `system_roles.go`: `runSystemBoxVerb` hands `box publish-record` to the
  host verb before `roleArgs`.
- `system.go`: `systemUsage` gains the synopsis and description lines.
- `internal/host/boxrecord.go` is not edited. `publishBoxRecord`,
  `boxRecordLoop` and its gate are byte for byte main's.

The verb's checks, in order:

1. The effective uid is not 0 (R1).
2. The box is not a fleet box (`shouldSkipPublisherElection`).
3. `CARLOS_DEPLOYMENT_BUCKET` is set. A deployment directory is refused: its
   check-then-rename cannot refuse an overwrite, and the verb never opens one.
4. The box's label is `metricsBoxLabel`, the loop's, and it is a valid box
   reference. This is the first host read (`/etc/machine-id`, or IMDS for a
   group box).
5. The store is opened with `openDeployStoreEnv` and must be a
   `release.S3Store`.
6. The record under the key is read through `boxRecordStore`, then this
   box's record is read from IMDS once.
7. If a record is there, enrollment's own verifier decides
   (`heldEvidence`). It calls `boxid.Verifier.Enroll` with the account from
   this box's current IMDS identity and `boxid.EmbeddedCerts`. The `Expect`
   holds the box label, the current AWS region, and the current instance id
   as `TofuInstanceID`. Enroll checks:
   - a strict version 1 envelope naming this box, with no other fields and
     nothing after it;
   - AWS's RSA-SHA256 signature under the certificate for the document's
     region;
   - the account, the region and the instance.

   There is no freshness check. An older record whose `pendingTime` or
   `instanceType` has since changed, or whose `written_at` is old, is kept.
   Its bytes are never touched, because the record cannot be replaced.
   Anything Enroll refuses is refused, with the reason named, and nothing is
   written. Keeping a record means only that valid evidence is in place. The
   roster and operator checks are still enrollment's.
8. If nothing is there, `publishBoxRecord` runs through `boxRecordStore` with
   the record already read. Its outcome is reported by what reached the
   bucket. A failure before any create says nothing was written. A failure
   after a create reached the bucket says the write may have happened. A
   create answered 412 whose stored bytes are exactly this run's (a retried
   create) is reported as published. A record that arrived after step 6 is
   held to the same verifier.

## Root-read census (TestPlan14RootReadCensus)

Overlay `internal/provision/testdata/plan14-root-reads-box-publish-record.tsv`,
loaded after `plan14-root-reads-region-ha-2b.tsv`.

The scan finds no read site in any new or changed unit, so no root-read unit
is added or changed. The only discrepancies were the two source envelopes.

| Unit | Disposition | Reason |
|---|---|---|
| `@coverage .` | re-pinned to `d123dce7…5402` | `system.go` (usage lines) and `system_roles.go` (dispatch) changed. Neither adds a read. |
| `@coverage internal/host` | re-pinned to `b1fb4b84…7ab3` | `boxpublish.go` is new. Its units have no read site of their own, and root is refused before any read (R1). |
| `internal/boxid/certs.go:EmbeddedCerts` | unchanged, accepted-legacy (J4, root-input) | The verb now calls it for the certificates enrollment verifies with. Its reads are the binary's own embedded files, its digest is unchanged, and root never reaches it through the verb (R1). |
| `internal/boxid` envelope | unchanged (`0c6243af…3794`) | `Verifier.Enroll` has no read site. internal/host already imported internal/boxid (`claimproduction.go` calls `EmbeddedCerts`), so the verb's import graph gains no package. Only `crypto/x509` is added, from the standard library. |
| legacy seal | unchanged (`fc584e11…1b5d`) | The overlay adds no `@legacy` row. |

## Record-write census (TestPlan9B8RecordWriteCensus)

The inventory is re-pinned by `internal/provision/box_publish_record_census_test.go`
(`plan14BoxPublishRecordWriteCensusInventory`, which the test uses). It
appends one entry to `plan14ActivationToolsWriteCensusInventory`.

| Unit | Kinds | Class | Reason |
|---|---|---|---|
| `internal/host/boxpublish.go:boxRecordStore.Put` | `def:Put,sel:Put` | `nonrecord` (R4) | It writes `control/boxes/<box>.json`, a box identity record, never an instance or sidecar record. It touches no record-key constructor or record path literal. It admits only a create-only Put on its one key. |

Digest `866978d6c6786a351bbeaf6f9e5382df0b3fd08cbb47aed0c01738f7c5120d7a`.

Before this branch, the one writer of `control/boxes/` was `boxid.Publish`,
reached only from `publishBoxRecord` in `boxRecordLoop`. The verb is a second
caller of the same chain, not a second writer: `boxid.Publish` (`nonrecord`,
`sel:Put`) is unchanged. `boxRecordStore` wraps the store the chain writes
through, and refuses before the bucket any Put on another key, any Put with an
etag, and every `Delete`, `List` and `ListDirs`. `Get` is admitted on the one
key only.

## Tests behind the claims

`internal/host/boxpublish_test.go` runs the real `S3Store` (through
`openDeployStoreEnv`) against `leasetest`'s S3 server. The server sits behind
a fake of the dev bucket's policy, which denies a PUT under `control/boxes/`
without `If-None-Match: *` and any DELETE there.

- Fresh box: the record is written at the right key with the right fields,
  by one create-only PUT.
- After the loop has published (R3): kept, no PUT, bytes unchanged.
- An older record, correctly signed for the same account, region and
  instance but with a different `pendingTime` and `instanceType`: kept as
  it is, no PUT, bytes unchanged.
- Anything enrollment's verifier refuses is refused, named, with no PUT and
  bytes unchanged. The signing certificates are generated the way boxid's
  own tests generate them. The cases:
  - a different account;
  - a different region (one that has a certificate);
  - another instance;
  - a bad signature (another key);
  - a missing signature;
  - a document altered after signing;
  - a different box;
  - version 2;
  - an unknown field;
  - trailing data;
  - not JSON;
  - a document that is not JSON.
- A record arriving between the verb's read and `publishBoxRecord`'s is held
  to the same verifier: a valid older one is kept, a forged one refused.
- The embedded production certificates cover eu-west-1.
- A different record created just before the PUT: the bucket's 412 stops it
  and nothing is overwritten.
- A create committed but answered 500: the SDK retries, gets 412, and the
  verb reports published.
- A create committed but answered 400: the verb says the write may have
  happened, and a second run reports it in place.
- Root (R1), a fleet box, and no S3 bucket (a directory set, or a non-S3
  store) are each refused before the machine id, the store or IMDS is
  touched, with zero bucket requests. The directory is never written.
- A group box publishes under its group reference, the loop's label.
- `boxRecordStore` (R4): zero S3 requests for a delete, a list, an
  overwrite, another box's key and any other key.
- `boxRecordLoop` still returns at once with region HA off, unparseable, or
  on a fleet box, and sends nothing.

`system_roles_test.go` runs `carlos system box publish-record` from
`runSystem` in a child process, and checks the refusal and the exit status.
