auth/cell_ref_admission.ts

Possession-gated cell ref admission — the gate cell_create, cell_update, and cell_clone run on the fact refs their data introduces.

Fact serving admits a read of a hash through any cell the reader can view whose refs carry it, and refs derive from data alone (derive_cell_refs). Without this gate anyone who can write a cell could name any hash they had seen (in a URL, a header, an audit row) and serve its bytes through their own cell — to the world, if the cell is public — and read a fact's existence off the serve answer. So a write may introduce only refs to facts its acting actor holds: a fact_holder row, recorded on every put that names the actor, identical bytes put by a second actor included.

Twin of the Rust fuz_cell_actions cell_ref_admission module.

view source

Declarations
#

2 declarations

CELL_REF_NOT_HELD_MESSAGE
#

auth/cell_ref_admission.ts view source

"cell data references a fact the caller does not hold" import {CELL_REF_NOT_HELD_MESSAGE} from '@fuzdev/fuz_app/auth/cell_ref_admission.js';

The refusal message — byte-identical with the Rust spine's.

require_new_refs_held
#

auth/cell_ref_admission.ts view source

(deps: QueryDeps, auth: RequestActorContext, base: readonly string[] | null, data: Json): Promise<void> import {require_new_refs_held} from '@fuzdev/fuz_app/auth/cell_ref_admission.js';

Refuse a write whose data introduces a fact ref the caller does not hold.

new_refs = derive_cell_refs(data) \ base, where base is what the write already carries — null on cell_create, the cell's current refs on cell_update, the source row's refs on each row cell_clone writes — so keeping refs someone else introduced, or cloning a viewable cell's refs, is admitted (the bytes were already readable through that cell). A global admin is not gated; a scoped admin is. The check runs on the write's transaction, only when new_refs is non-empty — a table probe, then one holder lookup for every new ref at once — and answers from holder rows only: an unheld hash reads the same whether or not a fact is stored under it.

A database without the fact chain (a consumer that mounts the cell verbs but no fact store) has no holders, so every new ref is refused there too — checked first, since naming a missing table would abort the transaction.

Runs as the last gate before the row is written: after the 404 masks, the per-kind validator, and the create authorizer.

deps

query deps on the write's transaction

auth

the acting caller

base

the refs the write already carries, null for none

type readonly string[] | null

data

the data the write stores

type Json

returns

Promise<void>

throws

  • ThrownJsonrpcError - 403 `forbidden` with reason `cell_ref_not_held` when a new ref is unheld

Depends on
#

Imported by
#