Version: v0.1
Date: 2026-08-23
Status: Scoping note. Records a live instance, corrects three earlier claims of mine, and names one thing to rule. Rules nothing. Markdown primary.
Occasion: a rendering identity found frozen in the dev database at 2026-08-23 20:56:26 UTC, by an actor id that resolves to no one.
Grounded on: agents/grantability.py read in full at engine 143fd6b; CC's read-only lookups across principals, contributors, host_account, engagements, memory_events.
Reads with: standing-notes/loomworks-scoping-finding-freeze-alert-v0_1; standing-notes/loomworks-standing-note-agent-accountability-v0_1 (STANDING); standing-notes/loomworks-standing-note-two-records-and-what-crosses-between-them-v0_1 (STANDING) §5.
The rendering agent identity was found frozen, with one lifecycle row recording that a person did it. That person does not exist. The id matches nothing in principals, contributors, host accounts, or anywhere else in the database.
Reading the code corrected my first reaction. Freezing is deliberately open to any caller — tightening is meant to be cheap and never wait for a human, and that is ruled. So an unidentified freeze is not a breach of the rule; there is no rule to breach.
What is unguarded is the other lane. Unfreezing is human-only by design, and the check that enforces it looks only at whether the actor says kind="person" — never at whether that person exists. A fabricated actor passes.
And there is a smaller finding underneath, which strengthens an argument I made yesterday on weaker grounds: a freeze cannot record why it happened. Not loses the reason — has nowhere to put one. The function takes no such argument.
Severity is bounded, honestly: none of these calls has an HTTP surface, so anyone reaching them already has database access and could change the row directly. The cost is not that someone got in. It is that the record now asserts a person acted, and cannot support it.
event_kind identity_frozen
actor_id 29c62e33-855c-4acb-98c5-2eb075dab9f8
actor_kind person
occurred_at 2026-08-23 20:56:26.703681 UTC
reason NULL
The id resolves to nothing — not principals, not contributors, not host_account, not engagements.created_by_person_id, not memory_events.actor_id. Nothing else in the system was written within five minutes either side.
Not from this session's testing. CC's own probe used a fresh uuid.uuid4(), and its rollback discipline is corroborated rather than asserted: the same pass minted a real session through the identical pattern, and agent_sessions is still zero. A broken rollback would have leaked both.
So the freeze is unexplained, and no request log exists to consult because the call has no HTTP surface.
Preserved rather than smoothed. All three were mine, all three were wrong, and two of them were in documents already filed.
2a. I said a freeze is human-only. It is not, deliberately. freeze_agent_identity's own docstring: any actor may call this, including a machine one. DR-15: a tightening never waits for a human… an agent that can stop another agent is not a widening; it is the direction the design wants to be cheap.
So the unidentified freeze breaks no rule about who may freeze. The asymmetry is the design: tightening open, widening closed.
2b. I said the freeze reason is "stored on the identity row and lost on unfreeze." False. freeze_agent_identity(*, agent_identity_id, actor, db, now) — there is no reason parameter. A freeze has never been able to record why.
This makes yesterday's finding/freeze/alert argument stronger, not weaker. That note argued the reason is fragile because unfreezing loses it. The truth is worse and simpler: the reason has nowhere to live at all. ck_agent_lifecycle_events_reason_present requires a reason only for refused and abandoned, so the schema is consistent with a function that cannot supply one.
2c. I framed this as an authorization failure on the freeze path. The authorization gap is real and it is on the unfreeze path (§3). What the freeze row shows is a provenance failure (§4). Different problems, different fixes, and conflating them would have pointed the work at the wrong lane.
unfreeze_agent_identity is human-only by design, per DR-15 and standing note O10, and enforces it with:
if not has_human_commit_authority(actor):
raise AgentCannotWidenAuthorityError(...)
That check tests actor.kind and nothing else. It never asks whether the id refers to anyone. So:
ActorRef(kind="person", id=uuid4())
satisfies the one gate protecting the only widening act in the system.
The docstring's claim — a machine actor cannot reach past this… it is not that widening is refused for machines and allowed under some condition; there is no condition — is true of a machine that declares itself. It is not true of a machine that declares itself a person, and nothing stops one.
This is the gap. Not the freeze.
The lifecycle row is not wrong. It faithfully records that a person-kind actor with that id froze the identity. Every field is accurate.
And the whole thing is unverifiable. An audit trail whose actor resolves to no one ends at a shrug.
This bears directly on a STANDING commitment. The two-records note carries provenance across to the operational record explicitly — every row carries who acted and when. An actor id that resolves to nothing is provenance in name only, and this is the first row in that table that demonstrates it.
It also bears on the accountability commitment, from an angle this week's work did not cover. That note requires every agent action to carry a responsible human. The agent side is now true — actor, grant, session, all recorded. The human side turns out to be unchecked. The work made agents accountable to humans and did not ask whether the humans are real.
Following from §2b, and worth stating on its own because it is not only a note-correction.
Today: an identity can be frozen, the act is recorded, and the record cannot say what prompted it. The rendering row is exactly this — someone stopped rendering platform-wide and the system holds no account of why.
Under the finding/freeze/alert shape, the freeze would cite a finding and the finding would carry the reason permanently. This row is the first real-world instance of that gap, found by looking at one row in a test set rather than by building.
Worth noting for the seed-ratification list: this is what the split buys, demonstrated rather than argued.
None of these functions has an HTTP surface. freeze_agent_identity, unfreeze_agent_identity, get_live_sessions and sweep_silent_sessions are reachable only from a Python process holding a database session.
So anyone who can call them can already write the row directly. A resolution check on the actor does not stop someone with database access; it stops a caller who reaches these functions through a future surface — an Operator Layer control, a script, an agent given a tool.
That is the honest severity: this is not a live breach, it is a control that will not hold when a surface is put in front of it. And a surface in front of freeze/unfreeze is an obvious next thing to build.
A second, smaller cost that applies today: every row written through these paths carries whatever actor the caller supplies. The record's trustworthiness is currently a property of who has shell access, not of anything the system checks.
7a. Require the actor to resolve. has_human_commit_authority — or a new check beside it — looks the id up and refuses if it matches no principal.
Closes §3 properly. Costs a database read on a path that currently does none, and needs a decision about which tables count as resolving — principals only, or contributors and host accounts too.
7b. Require it only at the widening lane. Leave has_human_commit_authority alone; add the resolution check inside unfreeze_agent_identity.
Narrower and matches the asymmetry the module already implements. Leaves every other caller of has_human_commit_authority unchanged, which may be right or may be the same hole elsewhere — this note has not checked who else calls it.
7c. Record resolvability rather than enforce it. Write a flag on the row: this actor resolved, or did not. Preserves evidence without refusing anything, which suits a system that would rather record than block. Weakest as a control and strongest as history.
7d. Give freeze a reason parameter. Independent of the above; addresses §5 rather than §3. Small. It is also the thing finding/freeze/alert would supersede, so doing it now may be building something that gets replaced.
Not an option: treating the rendering freeze as noise. Something froze a platform-wide identity and nothing says who or why. Whatever is ruled, that row stays.
It does not rule any option. It does not check who else calls has_human_commit_authority — §7b names that as unchecked. It does not identify who froze rendering; the record cannot say and neither can this note. It does not unfreeze it. It does not amend the finding/freeze/alert note, though §2b strengthens its argument and that note should carry the correction. And it does not claim a breach occurred — §6 is deliberate about that.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Scoping Note: An Actor That Resolves To No One — v0.1 — 2026-08-23