Commit Graph
8 Commits
Author SHA1 Message Date
Vikhyath Mondreti 7ea8692e7d fix(files): read a file nobody vouched for, as an untracked one already is (#6986)
* fix(files): read a file nobody vouched for, as an untracked one already is

A workspace file whose secret provenance could not be recorded was refused by every model and
runtime boundary at once — attachments, mounts, and the tool routes that parse, transcribe or
describe a file. A file that was never tracked at all returned exact-empty and worked fine.
Both say exactly as much about their contents: nothing. So the refusal was a permanent penalty
for having tried to record provenance and failed, with no way back, since nothing rewrites a
file's provenance but another content write.

Workspace files now do what the other durable surfaces do — proceed and record an audit entry
naming the surface and the count, so the people who own the secrets are told. The state is
distinguished from the four that really are unknown (missing row, version mismatch, stale
binding, malformed), which stay refused; only a current sidecar that says so reads as
unrecorded.

Absence survives a merge rather than dropping through to exact, so a derived file cannot
launder an unrecorded input into a positive claim. Script migration 0007 clears the files
already stranded in the old state, taking the parent lock first in id order to match the live
writer, with the unknown status re-checked under it so a concurrent write is never undone.

Nothing changes for a legacy file: no sidecar row still means untracked, still reads
exact-empty. The enforcement flag stays in place.

* fix(files): narrow an initialization to a status the column accepts

The union carries three states; the sidecar's CHECK constraint accepts two. Only the replace
path discriminated — initialize forwarded the status verbatim, so an 'unrecorded' would have
reached the database as a value it rejects, aborting the enclosing transaction rather than
writing a bad row. Nothing passes one today, which is why it belongs here and not in a caller.

Also names the storage mismatch on the type itself: a stored 'unknown' reads back as
'unrecorded', not as the union's 'unknown'. Same word, two layers, different meanings.

* fix(provenance): repair only what the reader calls an absence

The repair selected on status alone, which is wider than the branch it was meant to undo. The
reader answers 'unrecorded' only for a sidecar that is version 1, bound to the file's current
bytes, and holding a well-formed entries array; anything else is a fault it refuses and no
policy relaxes.

Clearing a fault was not a smaller claim but a larger one. Deleting the sidecar sets
secret_provenance_version to NULL, and a NULL version reads as exact-empty — so a file refused
because its provenance described bytes it no longer holds would have come back positively
vouched for, silently, with no audit entry. Four such rows exist in production today.

Both the candidate query and the delete now carry every condition, so a concurrent write
cannot lose the distinction between an absence and a fault. Stale, unversioned and malformed
sidecars stay refused exactly as the surface refuses them, and recover as they always have, on
the next content write.

* fix(provenance): store an absence apart from a refusal

Relaxing a stored `unknown` assumed it always meant "nobody recorded this". It did not. The
sidecar collapsed two opposite claims into one value: bytes nobody recorded, and bytes a writer
refused on purpose — a child of an archive whose parent provably held secrets, a generated
asset whose safety decision came back false, a transcode whose scanner knew secrets were
present and could not locate them. Relaxing the second would have walked known-secret-bearing
content through every model boundary at once.

The decision type already told them apart: a registry that never ran is `safe: true`, while
every refusal is `safe: false`. Only storage lost it. Writers now persist `unrecorded` and
`unknown` separately, readers relax the first and refuse the second, and the constraint is
widened to accept it.

Only workspace files store this distinction, deliberately. Every other durable surface produces
a non-exact sidecar from one condition — an incomplete incoming bundle or registry — which is
always an absence. Files are the only surface that derives one stored object from another, so
they are the only one that can refuse on purpose. The shared policy is unchanged and uniform:
read an absence and audit it, refuse a taint.

Also aligns the metadata batch classifier, which answered `unknown` where the single-file reader
answers `unrecorded`, leaving two classifiers describing one policy differently.

* test(provenance): assert the enforcement switch on a row it can act on

The test that pins "closing the surface refuses an unrecorded file again" used a stored
unknown, which the split had just turned into a taint. A taint is refused whatever the flag
says, so the assertion held with enforcement off too and proved nothing about the switch this
posture rests on. It now uses a recorded absence, and fails if the flag is ignored.

* fix(files): copy a recorded absence as an absence

The copy path required an exact source, so a stored unrecorded one fell in with the refusals
and the target was written as a taint the source never carried. A workspace fork or a chat file
copy therefore turned a readable file into a permanently refused one — the pathology this
surface exists to undo, reached by copying — and nothing rewrites a file's provenance but
another content write.

Safe past the scope checks it now precedes: those exist to stop one workspace's secret entries
landing in another's file, and a recorded absence has no entries to carry.

The legacy Function export marker keeps writing unknown. Its record had resolved secret names
in scope and an unreadable bundle, so whether that is an absence is not something this change
can establish, and guessing in the readable direction is what caused this round.
2026-08-22 15:05:39 -07:00
Vikhyath Mondreti b861062370 fix(provenance): clear the rows that went unknown after the first repair (#6982)
* fix(provenance): clear the rows that went unknown after the first repair

0005 is finished and will not run again — the runner records a name in
script_migrations and never offers it back, which is the contract a
run-once repair wants. But it cleared the backlog that existed at the
instant it ran, and the writer that produced that backlog kept running
until the fix in this branch. Nothing heals such a row in place, so each
one goes on reporting on every later read; a few dozen of them account
for thousands of log lines a week.

A second entry rather than deleting the first's tracking row: the
registry is append-only, and a repair that ran twice should say so
twice. It shares 0005's walk rather than restating it — the parent-first
lock ordering and the status re-check under that lock are subtleties
worth having once — and is idempotent, so it costs one empty query if
there is nothing left to repair.

* chore(provenance): record the deploy ordering the second pass depends on

promote-images needs migrate, so a script migration runs while the
previous image is still serving. A row an old instance creates between
this walk and the end of the rollout sits behind the cursor, and the
name is recorded on success, so it is never offered again. Widening the
walk would not help — the exposure is the minutes after it returns, not
the milliseconds during — so the requirement is to ship it in a release
after the writer fix is already promoted, which the file now says.
2026-08-22 13:35:06 -07:00
Vikhyath Mondreti fcea50deb0 fix(provenance): stop size limits silently dropping secret provenance (#6867)
* fix(provenance): stop size limits silently dropping secret provenance

A bundle-selection cap counted cells rather than rows, so a 25-column
table insert lost secret provenance for every row past 400 — the whole
batch was stamped unknown with nothing logged. The same number lived in
the sender, the runtime type guard, and the route contract.

Consolidate every provenance limit into one definition: an 8MB
serialized envelope and 10,000 distinct secrets. The pair had been
copied into seven modules under fourteen names, and several copies had
drifted into bounding inputs — rows, cells, files, chunks — rather than
the envelope.

Remove every limit that could refuse a legal payload, add write-side
cause logging and a workspace-visible audit entry when a read proceeds
on unrecorded provenance, and repair the existing unknown rows.

* fix(provenance): close a repair race and stop memory double-reporting

The repair matched sidecars by the id its page captured, so a
provenance-aware write committing between the snapshot and the delete
had its fresh exact sidecar removed and its marker cleared behind it —
a secret-bearing row left reading as legacy. The delete now re-checks
status, which under READ COMMITTED re-evaluates against the writer's
committed row so it no longer matches.

Walk the candidate set by keyset over row_id. A page whose rows were
all repaired concurrently clears nothing, and terminating on "cleared
nothing" ended the walk with the rest of the backlog untouched.

Memory reported unrecorded provenance twice, and counted records even
when the surface was enforced — auditing a fail-open read that had
actually failed closed.

* fix(provenance): take the repair's locks in the writer's order

The repair deleted the sidecar and only then updated its parent row,
while mutateTableRowsWithSecretProvenance locks user_table_rows up front
and upserts the sidecar inside the same transaction. Opposite orders, so
an overlapping write deadlocked and Postgres resolved it by aborting
either the deployment or somebody's table write.

Lock the parent first, in id order, matching lockTableRows. Holding that
lock is also what makes the status re-check decisive rather than racy:
the writer commits its sidecar and its marker under the same lock, so
once it is held the write is either wholly done or has not begun.
2026-08-19 15:42:33 -07:00
Vikhyath Mondreti 5cf1f9be84 improvement(provenance): cleanup secrets boundary (#6374)
* fix(secrets): preserve raw outputs with durable provenance

* improvement(provenance): cleanup boundary

* fix copy resources

* fix fork copies to work with provenance

* address comments

* fix
2026-08-07 17:12:17 -07:00
Vikhyath Mondreti 2d41360807 improvement(concurrency): limits configurable, docs updates (#5640)
* improvement(concurrency): limits configurable, docs updates

* remove dead tests

* limits self hosted vars
2026-07-13 13:11:05 -07:00
Vikhyath Mondreti ef7c8e24b2 feat(platform): settings permissions, admin, billing attribution (#5545)
* fix(invites): preserve active organization for external access

Keep organization activation server-owned so failed membership checks cannot clear a valid session context.

* feat(admin, billing, settings): cleanup settings visibility, billing actor resolution, new admin routes

* address comments

* chore(db): reset pending migrations before staging merge

Remove locally generated migrations so they can be regenerated against the latest staging schema without preserving stale snapshots or numbering.

* regen migrations

* address comments

* chore(db): reset generated migrations before staging merge

Remove this branch's generated migrations so they can be regenerated against the latest staging schema with fresh numbering.

* upgrade global work

* fix lint

* address comments

* legacy callbacks correctness

* address comments

* update

* guardrail attribution
2026-07-13 00:53:49 -07:00
Theodore Li 8c61720d33 chore(tables): remove tables-fractional-ordering feature flag (#5503)
* chore(tables): remove tables-fractional-ordering feature flag

* improvement(tables): drop dead position tracking from paste undo path

* improvement(tables): drop dead position from create-row undo records
2026-07-08 13:11:45 -04:00
Theodore Li 12fb4a9db1 feat(db): auto-apply tracked script data migrations in db:migrate (#5497)
* feat(db): auto-apply tracked script data migrations in db:migrate

* fix(db): reset session lock_timeout before script migrations, guard journal insert

* improvement(db): re-verify advisory-lock session before script migrations
2026-07-07 20:55:42 -04:00