Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/20596-service-storage-provenance-anchors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/service-storage': patch
---

Provenance comments in `service-storage` were re-anchored

Comment and docblock lines under `src/` that cited tracker numbers which no
longer resolve on GitHub now cite the commit in this repository's history that
decided the matter, and say in their own words what was decided. Comments
only: no type, schema, export, log or refusal text, or runtime behaviour changes.
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ describe('attachment access — beforeDelete (uploader or parent editor)', () =>
});

// ─────────────────────────────────────────────────────────────────────────
// #10091 — beforeUpdate: uploader or parent editor, + the attach rule on a
// [commit da891e0ef] beforeUpdate: uploader or parent editor, + the attach rule on a
// re-point. The delete gate's rule applied to the verb that could otherwise
// rewrite it away (the comment kit — derived from this one — has gated
// update since #4630; the source kit was missing the limb its derivative
Expand Down Expand Up @@ -618,7 +618,7 @@ describe('unscoped multi-delete (no id, no where) — #4757 through the wired en
});

// ─────────────────────────────────────────────────────────────────────────
// #10091 through the WIRED engine — the update verb.
// Commit da891e0ef's gate through the WIRED engine — the update verb.
//
// Same rig as the #4757 block above, driving `ql.update('sys_attachment', …)`
// end to end: the unscoped refusal reaches the handler through the
Expand Down Expand Up @@ -763,7 +763,7 @@ describe('unscoped multi-update (no id, no where) — #10091 through the wired e
// rebuilt a five-field projection of the caller's execution envelope before
// handing it to `ISharingService.canEdit`, whose contract declares the FULL
// envelope and whose doc block tells callers they "MUST NOT rebuild a subset
// of it" (#6523 / the #6206 ruling).
// of it" (commit aa4b90d9a, the full-envelope ruling).
// ─────────────────────────────────────────────────────────────────────────

/**
Expand Down Expand Up @@ -911,9 +911,9 @@ describe('#7145 — caller envelope forwarded to the sharing gate', () => {
);

const forwarded = canEdit.mock.calls[0]![2] as unknown as Record<string, unknown>;
// Every principal field survives — the #6523 contract's unit is the
// envelope, and #6206 forbids rebuilding a subset of it. `uploaded_by`
// stamping does not touch the context.
// Every principal field survives — the contract's unit is the envelope
// (commit aa4b90d9a), and the full-envelope ruling forbids rebuilding a
// subset of it. `uploaded_by` stamping does not touch the context.
expect(forwarded).toEqual(DELEGATED_PRINCIPAL_FIELDS);
// …and every middleware-private key resolved for `sys_attachment` is gone.
for (const key of OPERATION_PRIVATE_KEYS) expect(forwarded).not.toHaveProperty(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type {
* on the parent; v1 enforces read visibility — strictly better than
* nothing, edit-parity is a tracked follow-up.) `uploaded_by` is
* server-stamped from the session — a client-supplied value never wins.
* - beforeUpdate (#10091): the caller must be the uploader OR hold edit on
* - beforeUpdate (commit da891e0ef): the caller must be the uploader OR hold edit on
* the parent record — the delete rule, applied to the verb that could
* otherwise rewrite the other two gates away: an ungated update let any
* member re-point `parent_id` at a record they cannot see, or rewrite
Expand Down Expand Up @@ -124,9 +124,9 @@ function asIdList(id: unknown): Array<string | number> | null {
* session snapshot lacks `permissions`, which sharing bypasses need.
*
* [#7145] Forwarded as the full envelope, which is what `ISharingService`
* declares for every parameter this value is handed to and what the #6206
* declares for every parameter this value is handed to and what the full-envelope
* ruling requires of every caller: they "MUST NOT rebuild a subset of it"
* (#6523). The five-field projection this replaced (`userId` / `tenantId` /
* (commit aa4b90d9a). The five-field projection this replaced (`userId` / `tenantId` /
* `positions` / `permissions` / `isSystem`) was doing two jobs at once, and
* only one of them was correct — same defect, same kit, one package over from
* `comment-access-hooks.ts` (#7141 / PR #7143), which this mirrors:
Expand Down Expand Up @@ -467,7 +467,7 @@ export function installAttachmentAccessHooks(
// the mechanism was ruled onto `beforeUpdate` as well; the refusal stays
// PER REGISTRATION. This one carries #4757's delete refusal under its
// grandfathered `ATTACHMENT_DELETE_DENIED` envelope; the `beforeUpdate`
// registration above declares the update-verb refusal (#10091) under the
// registration above declares the update-verb refusal (commit da891e0ef) under the
// standard catalog code — the same both-verbs pairing the derived
// comment kit ships.
{ object: 'sys_attachment', packageId: PACKAGE_ID, dispatchUnscopedMultiWrite: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ export async function findFileHolder(

/**
* The BATCHED form of {@link findFileHolder} — "which of these files is still
* held?" — for callers holding many rows at once (#11427).
* held?" — for callers holding many rows at once (commit c3c72a4bc).
*
* Record file-field hydration is such a caller: it must reach the same verdict
* the download path reaches (#10246) or one `sys_file` row gets two answers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
* `createRecordOrganizationResolver`, and the divergence from the precedent is
* the point of this paragraph. That resolver's limb 0 reads
* `tenancy.organizationField`, a STAMP-ONLY key whose consumers are scope-pinned
* by the #8778 ruling (widened by name on cloud#1395) to exactly three
* by its ruling (commit 7901b2dd2; widened by name on cloud#1395) to exactly three
* platform-row writers; a fourth needs its own maintainer ruling. It would also
* be the WRONG question here. That key answers "which column says who this row
* is ABOUT"; this sweep needs "which column is this subject WALLED by", because
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,10 +576,10 @@ describe('[#15352] §5b — a `tenancy` service that was REGISTERED and FAILED i
* the digits are now asserted, and they are the declared ones.
*
* What happened: the authorizer always re-raised the brand
* (`isAuthzStoreUnavailableError(err) ⇒ throw`, #13279), and
* (`isAuthzStoreUnavailableError(err) ⇒ throw`, commit 6a180e42d), and
* `registerStorageRoutes`' `authorizeDownload` absorbed that re-raise one
* frame up in `catch { verdict = 'deny' }`, rendering an outage as the gate's
* own capability refusal — the confusion #13279 exists to prevent. That
* own capability refusal — the confusion commit 6a180e42d was made to prevent. That
* `catch` now RELAYS the declared envelope instead (⛔ not a bare re-raise:
* the route's outer `catch` would answer `500 INTERNAL`, and the shared
* render for an escaped envelope is #16545 and has not landed).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const SYSTEM_CTX = { isSystem: true, [RAW_FILE_VALUES_CONTEXT_KEY]: true } as co
*
* This mirrors `StorageMetadataStore`'s `StorageWriteContext` threading
* (`createFile` #12745, `createSession` #12928, the update/delete halves
* #13178) rather than inventing a second convention: the caller hands the
* in commit f087c376f) rather than inventing a second convention: the caller hands the
* engine the organization it is acting in as an execution context, and the
* platform's existing insert-side chokepoint decides the rest. ⛔ The
* organization is NOT written onto the payload here — whether this object has
Expand Down
12 changes: 6 additions & 6 deletions packages/services/service-storage/src/metadata-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface FileRecord {
* every walled deployment — a warning naming exactly this defect ("writes will
* not be tenant-isolated").
*
* ## What the SAME context does on update / delete (#13178)
* ## What the SAME context does on update / delete (commit f087c376f)
*
* ⚠️ Not the same thing, and the difference is the whole reason the
* `update`/`delete` doors are not a copy-paste of the insert ones. Write-side
Expand Down Expand Up @@ -350,7 +350,7 @@ export class StorageMetadataStore {
/**
* Update one `sys_file` row.
*
* `context` carries the acting organization (#13178). It is the SAME channel
* `context` carries the acting organization (commit f087c376f). It is the SAME channel
* {@link createFile} opened in #12745 — `{ context: { tenantId } }` on the
* engine options bag — and this door is the `update` half of that insert
* that #12745 did not repair. What the value MEANS differs by verb, and
Expand Down Expand Up @@ -381,7 +381,7 @@ export class StorageMetadataStore {
// stand up a second isolation mechanism outside the driver that owns
// the one real one. A no-engine deployment has no wall to be on the
// wrong side of (the same sentence `createFile`'s stand-in already
// makes), so this branch is unchanged by #13178.
// makes), so this branch is unchanged by commit f087c376f.
this.files.set(id, merged);
return merged;
}
Expand All @@ -395,7 +395,7 @@ export class StorageMetadataStore {
/**
* Delete one `sys_file` row.
*
* `context` carries the acting organization (#13178) — the `delete` half of
* `context` carries the acting organization (commit f087c376f) — the `delete` half of
* #12745's insert, repaired for the same reason and through the same
* channel as {@link updateFile}. See {@link StorageWriteContext} for what
* the value does on this verb (it scopes the statement; it stamps nothing).
Expand Down Expand Up @@ -480,7 +480,7 @@ export class StorageMetadataStore {
/**
* Update one `sys_upload_session` row.
*
* `context` carries the acting organization (#13178) — the `update` half of
* `context` carries the acting organization (commit f087c376f) — the `update` half of
* the insert #12928 repaired, the `sys_upload_session` sibling of
* {@link updateFile}. Same channel, same chokepoint, and the same split
* between stamping and scoping that {@link StorageWriteContext} records.
Expand Down Expand Up @@ -522,7 +522,7 @@ export class StorageMetadataStore {
/**
* Delete one `sys_upload_session` row.
*
* `context` carries the acting organization (#13178) — the `delete` half of
* `context` carries the acting organization (commit f087c376f) — the `delete` half of
* #12928's insert. See {@link StorageWriteContext} for what the value does
* on this verb.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
* ## What was measured, and why this row came first
*
* `buildFileReadAuthorizer` re-raises `AuthzStoreUnavailableError` rather than
* returning `'deny'` (#13279). `registerStorageRoutes`' `authorizeDownload`
* returning `'deny'` (commit 6a180e42d). `registerStorageRoutes`' `authorizeDownload`
* then wrapped the whole authorizer call in `catch { verdict = 'deny' }` one
* frame up, so the re-raise was absorbed and the outage rendered as
* `403 FILE_DOWNLOAD_DENIED` / `403 ATTACHMENT_DOWNLOAD_DENIED`. Fail-CLOSED,
* never an admission — but indistinguishable on the wire from a genuine
* refusal, which is the exact confusion #13279 exists to prevent, and the worst
* refusal, which is the exact confusion commit 6a180e42d was made to prevent, and the worst
* shape in this card's six-site census (the datasource and settings families
* lost the envelope into a 500; this one lost it into a *verdict*).
*
Expand Down
14 changes: 7 additions & 7 deletions packages/services/service-storage/src/storage-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type FileReadVerdict = 'allow' | 'deny' | 'unauthenticated';
* session with no active organization resolves to `undefined` rather than to a
* guess.
*
* ⚠️ Since #13178 the same value also travels on this door's `updateFile` /
* ⚠️ Since commit f087c376f the same value also travels on this door's `updateFile` /
* `updateSession` calls, where it does something DIFFERENT — it scopes the
* statement instead of stamping a column, so a row belonging to another
* organization is no longer reachable (see `StorageWriteContext`). Two
Expand Down Expand Up @@ -196,9 +196,9 @@ export function registerStorageRoutes(
} catch (err) {
// [#15999, ruling item 3] An UNREADABLE authorization store is an outage,
// not a verdict. `buildFileReadAuthorizer` already re-raises the brand
// rather than returning `'deny'` (#13279) — and until now this `catch`
// rather than returning `'deny'` (commit 6a180e42d) — and until now this `catch`
// absorbed that re-raise one frame up and rendered it as this gate's own
// `403`, which is precisely the confusion #13279 exists to prevent: an
// `403`, which is precisely the confusion commit 6a180e42d was made to prevent: an
// outage answered as a capability denial, indistinguishable on the wire
// from a genuine refusal.
//
Expand Down Expand Up @@ -452,7 +452,7 @@ export function registerStorageRoutes(
// ---------------------------------------------------------------------------
httpServer.post(`${basePath}/upload/complete`, async (req: IHttpRequest, res: IHttpResponse) => {
try {
// [#13178] Bound, not discarded: this handler already resolved the
// [commit f087c376f] Bound, not discarded: this handler already resolved the
// session and threw the value away, which is what left the commit
// statement unscoped and raising `[tenant-audit]`.
const session = await requireUploadSession(req, res);
Expand Down Expand Up @@ -592,7 +592,7 @@ export function registerStorageRoutes(
// ---------------------------------------------------------------------------
httpServer.put(`${basePath}/upload/chunked/:uploadId/chunk/:chunkIndex`, async (req: IHttpRequest, res: IHttpResponse) => {
try {
// [#13178] Bound rather than discarded — see the commit door above.
// [commit f087c376f] Bound rather than discarded — see the commit door above.
// Named `authSession` because `session` below is the sys_upload_session
// ROW; these are two different things and the handler needs both.
const authSession = await requireUploadSession(req, res);
Expand Down Expand Up @@ -680,7 +680,7 @@ export function registerStorageRoutes(
// ---------------------------------------------------------------------------
httpServer.post(`${basePath}/upload/chunked/:uploadId/complete`, async (req: IHttpRequest, res: IHttpResponse) => {
try {
// [#13178] Bound rather than discarded — see the commit door above.
// [commit f087c376f] Bound rather than discarded — see the commit door above.
const authSession = await requireUploadSession(req, res);
if (authSession === false) return;
const writeContext: StorageWriteContext = { organizationId: authSession?.organizationId };
Expand Down Expand Up @@ -750,7 +750,7 @@ export function registerStorageRoutes(
// ---------------------------------------------------------------------------
httpServer.get(`${basePath}/upload/chunked/:uploadId/progress`, async (req: IHttpRequest, res: IHttpResponse) => {
try {
// [#13178] Bound rather than discarded — the progress door can WRITE
// [commit f087c376f] Bound rather than discarded — the progress door can WRITE
// (`expireIfPastDeadline` statuses the row `expired`), so it owes the
// same context the other two write doors do.
const authSession = await requireUploadSession(req, res);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ describe('StorageServicePlugin: sys_file orphan lifecycle wiring (#2755)', () =>

// Lifecycle hooks (afterDelete/afterInsert, plus afterUpdate since #10171
// gave the update verb its detach leg) + access hooks
// (beforeInsert/beforeUpdate/beforeDelete, #10091 added the update verb)
// (beforeInsert/beforeUpdate/beforeDelete; commit da891e0ef added the update verb)
// — see attachment-lifecycle.ts and attachment-access-hooks.ts.
//
// [#10240] There is exactly ONE `beforeDelete` here, and it is the access
Expand Down
14 changes: 7 additions & 7 deletions packages/services/service-storage/src/storage-service-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ export class StorageServicePlugin implements Plugin {
// deployment flag, fresh — before any byte is deleted.
installFileReferenceHooks(engine as any, () => this.storage, ctx.logger);
// "Is anything still holding this tombstone?" for RECORD FILE-FIELD
// HYDRATION (#11427). The download path got this question in #10246;
// HYDRATION (commit c3c72a4bc). The download path got this question in #10246;
// the record read kept the older `status === 'committed'` rule, so one
// `sys_file` row answered 200 at `/files/:id` and a bare id inside a
// record payload — which UI and export render as "no attachment".
Expand Down Expand Up @@ -1045,7 +1045,7 @@ function buildAuthSessionResolver(
* SUPPORTED and its behaviour here is exactly what it was.
*
* The throw is raised inside the authorizer's own `try`, so it takes the
* #13279 relay that block already runs for the identical fault one seam over
* relay that block has run since commit 6a180e42d for the identical fault one seam over
* (`isAuthzStoreUnavailableError(err)` re-raises instead of returning
* `'deny'`). Deliberately NOT a second net.
*
Expand All @@ -1054,9 +1054,9 @@ function buildAuthSessionResolver(
* authorizer in `catch { verdict = 'deny' }`, so the re-raise was absorbed one
* frame up and a failed posture read rendered as the download gate's own 403
* refusal — fail-CLOSED, never an admission, but wearing the costume of a
* capability denial, which is the confusion #13279 exists to prevent. That
* flattening was PRE-EXISTING (it had swallowed the #13279 permission-store
* outage at this door since that card landed, out of the same `catch`) and was
* capability denial, which is the confusion commit 6a180e42d was made to prevent. That
* flattening was PRE-EXISTING (it had swallowed the branded permission-store
* outage at this door since commit 6a180e42d landed, out of the same `catch`) and was
* repaired by #15999's ruling item 3: the `catch` now RELAYS the declared
* `503` / `SERVICE_UNAVAILABLE` envelope. The pin below still asserts the
* outage CLASS — never 200, never a minted capability — and its 403 arm retired
Expand Down Expand Up @@ -1146,7 +1146,7 @@ function buildFileReadAuthorizer(
// posture-conditional API-key refusals unreachable here — see
// `resolveAdmissionTenancyPosture` above for the classification, and for
// why a quiet `catch` at this seam would be the defect rather than the
// fix. Raised INSIDE this `try`, so an outage takes the #13279 relay in
// fix. Raised INSIDE this `try`, so an outage takes the relay (commit 6a180e42d) in
// the `catch` below rather than a new net.
const tenancyPosture = await resolveAdmissionTenancyPosture(registry);
const authz = await resolveAuthzContext({ ql: engine, headers, getSession, tenancyPosture });
Expand Down Expand Up @@ -1226,7 +1226,7 @@ function buildFileReadAuthorizer(
}
return 'deny';
} catch (err) {
// [#13279] A permission-store outage is not a read verdict. Re-raised so
// [commit 6a180e42d] A permission-store outage is not a read verdict. Re-raised so
// the file-read door answers the 503 it is instead of a silent 'deny'
// indistinguishable from a genuine refusal.
if (isAuthzStoreUnavailableError(err)) throw err;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Four doors on this object have been given the acting organization one card
// at a time — `createFile` (#12745), `createSession` (#12928), and the
// `update`/`delete` halves (#13178) — and all four run through
// `update`/`delete` halves (commit f087c376f) — and all four run through
// `StorageMetadataStore`, which threads a `StorageWriteContext` into
// `context.tenantId` so the platform's insert-side chokepoint can stamp the
// column. These two bypass that store entirely:
Expand Down
Loading
Loading