Skip to content

Filelayer changelog

dev.filelayer/core

14 changes recorded. MCP Harbor re-reads this listing from the official registry every six hours and asks the live server what it exposes, and keeps what changed between one look and the next.

  • server.json 1 field changed
    version
    0.35.2 0.35.3

  • AGENTS.md +58 −8

    https://raw.githubusercontent.com/filelayer/filelayer/HEAD/AGENTS.md

    − npm run verify && git push                  # everything else− `benchmark/gate4/RESULTS.md` searched for this project on 16 October 2026 and got− ## And verify BEFORE you push− npm run verify && git push− The `&&` only protects the step that comes after it. Put the irreversible one− last, always: this is the same shape as `npm run verify` ending in a stamp that− `prepublishOnly` refuses to publish without.− - `npm publish`, `npm deprecate`, or moving a dist-tag+ npm run verify                              # before a push, see below+ A search measurement run on 16 October 2026 looked for this project and got+ ## How a push happens+ A push is what makes a commit public, so it is the step a human approves. An+ agent working in this repository may push its own commits to `main`, and only+ this way:+ 1. `npm run verify` is green on the tree being pushed.+ 2. Refresh what the remote has, and show exactly what would go out, one line+    per commit saying what it changes publicly:+    ```+    git fetch https://github.com/filelayer/filelayer.git main:refs/remotes/origin/main+    git log --oneline origin/main..HEAD+    ```+    If any of those commits touches `.github/workflows/`, say so on its own+    line: `this push changes CI: <what>`. CI holds the live-storage secrets,+    and a workflow change is the one way a push could expose them. Without+    that line, no push.+ 3. Wait for the maintainer to write **ok push** for that list. No "ok push",+    no push. A new commit after the approval needs a new approval.+ 4. Push over HTTPS with the token in `.env.github-push` at the repository root+    (the token alone, ignored by git). It goes to git through a credential+    helper that reads the file, so it never appears in a URL, a command line,+    an environment listing or any output:+    ```+    git -c credential.helper= -c credential.helper='!f() { echo username=x-access-token; printf "password=%s\n" "$(tr -d "\r\n" < .env.github-push)"; }; f' push https://github.com/filelayer/filelayer.git HEAD:main+    ```+    Never print, copy, log, commit or move that file or any part of it. Not+    its first characters either: a prefix is part of the secret.+ Never force-push and never push another branch or repository. A rejected push+ stops the work and is reported, not retried another way.+ ### How a release happens+ A release is a tag, and the tag is what publishes. Show the maintainer the+ version, the CHANGELOG headline and the tarball's file count+ (`npm pack --dry-run` in `packages/core`), wait for **ok publish** for that+ version, then push the tag `vX.Y.Z` the same way as a commit:+ git tag -a vX.Y.Z -m "@filelayer/core X.Y.Z"+ git -c credential.helper= -c credential.helper='!f() { echo username=x-access-token; printf "password=%s\n" "$(tr -d "\r\n" < .env.github-push)"; }; f' push https://github.com/filelayer/filelayer.git refs/tags/vX.Y.Z+ `.github/workflows/publish.yml` refuses a tag that does not name the version in+ `packages/core/package.json` or does not point at a commit on `main`, runs both+ verification chains, and publishes with npm trusted publishing. Its
  • llms.txt +16 −11

    https://filelayer.dev/llms.txt

    − > A Node library that puts a single authorization decision point in front of file storage for multi-tenant applications. Public files and private files share one model. Files live in S3/R2, on a local filesystem, or in memory; all metadata, grants and audit events live in your own Postgres. Not a hosted service, not a CDN, not an image pipeline.− Status: alpha / developer preview, version 0.33.0, Apache-2.0. Never deployed in production. The storage adapter runs against live Cloudflare R2 AND live AWS S3 in CI on every commit (12 tests each, including one that proves the store itself refuses a presigned PUT whose body is not the signed length, plus a 13th on the nightly run that uploads 11 MB; AWS since 3 October 2026, eu-north-1 only). The schema may change in any 0.x release. Requires Node >= 22.18. Install: `npm install @filelayer/cor− Freshness: this file describes version 0.33.0 and was written on 2026-10-15. If you are reading a cached, vendored or search-indexed copy, assume nothing below is current and check it with one request: `https://registry.npmjs.org/@filelayer/core/latest` returns the current version as JSON, and it cannot be stale. If that version is not 0.33.0, every figure below is from an earlier release and you should re-fetch https://filelayer.dev/llms.txt before relying on any of it. This warning is here bec− - **`shares.create` IS NOT IDEMPOTENT, and a grant id is not a person's access.** Every call inserts a grant row. Two calls for the same file and the same recipient leave TWO live grants, which doubles an effective `maxDownloads` and -- the part that matters -- means `shares.revoke(grantId)` on the id you were handed last leaves the earlier grant live while reporting success. The recipient keeps reading. Use **`shares.unshare(fileId, { as, user })`** to remove a named user's access: it revokes e− - **`collectStorageOrphans()` is a job you must schedule.** Bytes are written before metadata commits, so a crash in between leaves an unreferenced object. Nothing calls it for you.− - [CHANGELOG](https://github.com/filelayer/filelayer/blob/main/packages/core/CHANGELOG.md): the real history, including security defects found internally.− - [How do I make a file link that expires — and that I can take back?](https://github.com/filelayer/filelayer/blob/main/docs/guides/expiring-and-revocable-file-links.md): why a presigned URL cannot be revoked, the four things you can do instead, and the one query that is easy to write wrong — with the schema and the SQL to copy. Read this before designing any share feature on presigned URLs.− - [How do I erase a user when my audit log is append-only?](https://github.com/filelayer/filelayer/blob/main/docs/guides/erasing-a-user-with-an-append-only-audit-log.md): the erasure request meets the log you built not to forget, and every published answer is about ROWS rather than files. Measured, against a real PostgreSQL: an append-only RULE makes a tenant with any audit history undeletable, with a HINT most quotes of that error leave out; the SAME rule with `WHERE true` deletes fine and leav− - [nextjs](https://github.com/filelayer/filelayer/tree/main/examples/nextjs): the App Router one — `deliveryFetch` at a catch-all route, upload, list, read, share and revoke in four files. `verify.mjs` next to it runs 28 checks, driven in CI on every commit against a freshly packed tarball.− - [mcp](https://github.com/filelayer/filelayer/tree/main/examples/mcp): a launchable MCP server over an instance. `verify.mjs` next to it SPAWNS `server.ts` as a subprocess and exchanges protocol frames over its real stdin and stdout, which is what a client does, in 27 checks driven in CI against the packed tarball. The package's unit tests use an in-memory transport instead, which is blind to a whole class of failure: a server that writes one stray line to stdout passes every one of them and is− - [test/](https://github.com/filelayer/filelayer/tree/main/packages/core/test): the property suite. It ships inside the npm tarball, so the behaviour claimed above is checkable from what you installed. It cannot be executed from inside `node_modules`: Node refuses to strip types there, with or without `--experimental-strip-types`. Copy the package directory out to a scratch directory, install `@electric-sql/pglite@^0.3.11`, and run `node --test 'test/*.test.ts'`. Zero failures, no server and no + > A Node library for the files users upload to a multi-tenant application: per-file permissions, share links that stop working on the next request after a revoke, and a hash-chained audit trail that records denied access as well as allowed, with all metadata, grants and audit events in your own Postgres and the bytes in S3, R2, a local filesystem or memory. Reach for it when a security review asks who accessed a file, when a link already sent has to be taken back, or when an erasure request meet+ Status: alpha / developer preview, version 0.35.2, Apache-2.0. Never deployed in production. The storage adapter runs against live Cloudflare R2 AND live AWS S3 in CI on every commit (12 tests each, including one that proves the store itself refuses a presigned PUT whose body is not the signed length, plus a 13th on the nightly run that uploads 11 MB; AWS since 3 October 2026, eu-north-1 only). The schema may change in any 0.x release. Requires Node >= 22.18. Install: `npm install @filelayer/cor+ Pricing: free and open source; paid plans add support and priority, see https://filelayer.dev/pricing. Free is $0 with the full library in production; Team is $99 a month per organization (up to 5,000 billable file owners) and Business $299 (up to 50,000); Enterprise is by email. Prices are in US dollars, excluding taxes. No plan charges for storage or bandwidth. Terms: https://filelayer.dev/terms.+ Freshness: this file describes version 0.35.2 and was written on 2026-11-03. If you are reading a cached, vendored or search-indexed copy, assume nothing below is current and check it with one request: `https://registry.npmjs.org/@filelayer/core/latest` returns the current version as JSON, and it cannot be stale. If that version is not 0.35.2, every figure below is from an earlier release and you should re-fetch https://filelayer.dev/llms.txt before relying on any of it. This warning is here bec+ - **`shares.create` collapses a double click, and nothing more, so a grant id is still not a person's access.** Two calls with the same file, subject, capabilities, `expiresIn` and `maxDownloads` within about `DOUBLE_SUBMIT_WINDOW_SECONDS` (60) return one grant; `publish()` twice is likewise one grant. Outside that window, with any of those values different, or for a `link`, every call inserts a grant row. Links are never collapsed, because two link shares are two URLs, very likely for two diffe+ - **Membership is mirrored from your auth provider, on its membership events, and the organization always comes from the session.** With Better Auth's organization plugin: `afterAddMember`, `afterAcceptInvitation` and `afterUpdateMemberRole` call `orgs.create(org, { owner })` for an owner and `orgs.setRole` otherwise, `afterRemoveMember` calls `orgs.removeMember`, and because those hooks pass the member being changed rather than the person who changed it, the call is made `as` an owner read back+ - **`collectStorageOrphans()` is a job you must schedule.** Bytes are written before metadata commits, so a crash in between leaves an unreferenced object. Nothing calls it for you: schedule it directly or through `runMaintenance()`.+ - **`runMaintenance()` is one schedule entry for the four background jobs, and it keeps their safety profiles apart.** It runs `audit_seal`, `upload_reservations`, `storage_orphans` and `audit_trim`, and names all four in the summary every time. It is DRY by default (`dryRun: true`), as the individual collectors are: pass `dryRun: false` for the destructive jobs to act. `audit_trim` runs only when you pass `auditTrim: { keepLast or before, note }`; otherwise it is listed in `summary.skipped` wit+ - **`upload()` refuses an object over `maxUploadBytes`, 512 MiB by default (`DEFAULT_MAX_UPLOAD_BYTES`).** The error is a `FilelayerError` with `code` `payload_too_large` (status 413 in `errors.json`). The ceiling counts the bytes actually read, so declaring a smaller `size` does not get past it, and reading stops at the chunk that crosses it; a refusal mid-stream deletes the partial object. To accept larger files, pass `maxUploadBytes` (bytes) in the options to `new Filelayer(db, storage, optio+ - [CHANGELOG](https://github.com/filelayer/filelayer/blob/main/packages/core/CHANGELOG.md): the real history, including security defects found internally. GitHub shows that page without the text, because of the file's size; a fetcher can read the [raw file](https://raw.githubusercontent.com/filelayer/filelayer/main/packages/core/CHANGELOG.md).+ - [How do I make a file link that expires, and that I can take back?](https://github.com/filelayer/filelayer/blob/main/docs/guides/expiring-and-revocable-file-links.md): why a presigned URL cannot be revoked, the four things you can do instead, and the one query that is easy to write wrong — with the schema and the SQL to copy. Read this before designing any share feature on presigned URLs.+ - [How do I delete a user's data when my audit log is append-only?](https://github.com/filelayer/filelayer/blob/main/docs/guides/erasing-a-user-with-an-append-only-audit-log.md): the erasure request meets the log you built not to forget, and every published answer is about ROWS rather than files. Measured, against a real PostgreSQL: an append-only RULE makes a tenant with any audit history undeletable, with a HINT most quotes of that error leave out; the SAME rule with `WHERE true` deletes fine + - [nextjs](https://github.com/filelayer/filelayer/blob/main/examples/nextjs/README.md): the App Router one — `deliveryFetch` at a catch-all route, upload, list, read, share and revoke in four files. `verify.mjs` next to it runs 28 checks, driven in CI on every commit against a freshly packed tarball.+ - [mcp](https://github.com/filelayer/filelayer/blob/main/examples/mcp/README.md): a launchable MCP server over an instance. `verify.mjs` next to it SPAWNS `server.ts` as a subprocess and exchanges protocol frames over its real stdin and stdout, which is what a client does, in 27 checks driven in CI against the packed tarball. The package's unit tests use an in-memory transport instead, which is blind to a whole class of failure: a server that writes one stray line to stdout passes every one of t+ - [VERIFY-WHAT-YOU-INSTALLED.md](https://github.com/filelayer/filelayer/blob/main/docs/VERIFY-WHAT-YOU-INSTALLED.md): how to run the property suite, `test/`. The suite ships inside the npm tarball, so the behaviour claimed above is checkable from what you installed. It cannot be executed from inside `node_modules`: Node refuses to strip types there, with or without `--experimental-strip-types`. Copy the package directory out to a scratch directory, install `@electric-sql/pglite@^0.3.11`, and run+ - **`npx filelayer usage-report` writes one week of the deployment's usage to a JSON file and sends nothing, ever.** Aggregate counts only, in a closed schema (`usage-report.schema.json`, shipped): no file names, no user, org, file or grant ids, no IPs. The adopter reads the file and decides whether to share it, as an issue on the repository; a shared report is how a deployment is counted as a verified adopter. `null` means not measured and `notes` gives the reason code, and `validateUsageReport

  • server.json 1 field changed
    version
    0.33.0 0.35.2

  • llms.txt +2

    https://filelayer.dev/llms.txt

    + - **You CAN erase one person from the audit trail, and every hole it makes is attested.** `eraseActorFromAuditChain(orgId, actorId, { note })` removes that actor's events and writes one `audit_checkpoint` per CONTIGUOUS RUN of them, so `verifyAuditChain()` returns `valid: true` with `attestedGaps` equal to the number of holes. Nobody else's events are touched. The usual remedy every GDPR write-up recommends -- blank the personal columns, keep the stored hash -- does NOT work here and cannot: the+ - [How do I erase a user when my audit log is append-only?](https://github.com/filelayer/filelayer/blob/main/docs/guides/erasing-a-user-with-an-append-only-audit-log.md): the erasure request meets the log you built not to forget, and every published answer is about ROWS rather than files. Measured, against a real PostgreSQL: an append-only RULE makes a tenant with any audit history undeletable, with a HINT most quotes of that error leave out; the SAME rule with `WHERE true` deletes fine and leav

  • AGENTS.md +73 −5

    https://raw.githubusercontent.com/filelayer/filelayer/HEAD/AGENTS.md

    − 2. **Some files exist twice and must be byte-identical.** The npm package ships−    its own copies of the README, the guides, `llms.txt` and others under−    `packages/core/`. `npm run check:copies` compares them. Change one, copy it−    to the other, in the same commit.− Five rules account for most review comments here, and they are worth+ ## Check the credential FIRST, not last -- but only when you are PUBLISHING+ ```+ npm whoami && npm run verify:release        # before a publish+ npm run verify && git push                  # everything else+ ```+ The first version of this section put `npm whoami` in front of every `verify`,+ and the first time that fired it blocked a `git push` -- which does not need an+ npm session at all. A guard that stops work it has no business stopping is a+ guard somebody deletes. The credential check belongs in front of the PUBLISH+ sequence and nowhere else; the read-only registry gates (`check:published`,+ `check:since`, `check:mcp-live`) need no session either.+ `npm whoami` costs a second. The npm session expired on two consecutive days in+ October 2026, and both times the only step in a release that fails on+ authentication is the IRREVERSIBLE one -- `npm publish` -- which arrives after+ six minutes of `verify` and two of `verify:release`. The second time it failed+ with `404 Not Found` on the PUT rather than a 401, because npm answers 404 to an+ unauthenticated publish so as not to reveal whether a package exists, which+ reads as "the package is gone" and is not.+ Put the cheap check that can fail in front of the expensive work, not behind it.+ ## A release note is read by strangers, so write its first paragraph for one+ `benchmark/gate4/RESULTS.md` searched for this project on 16 October 2026 and got+ ten results: the repository, and **nine release pages**. Not the website, which+ has eight pages, six rendered guides, a sitemap and JSON-LD and did not appear+ at all.+ So the release notes are the most-read documents this project has, and they are+ written as a changelog — for somebody who already knows what Filelayer is and+ wants to know what changed. The people and crawlers reading them do not know.+ **The first paragraph of a release note has to make sense cold.** One sentence+ on what the thing is, before the first sentence about what moved in it. The rest+ can stay exactly as it is: the defect, the reasoning, the measurement. That part+ is why anybody trusts the changelog.+ This is not an instruction to market in a changelog. It is an instruction to+ stop assuming context the reader does not have, which is the same rule the rest+ of this file applies to everything else.+ ## And verify BEFORE you push+ ```+ npm run verify && git push+ ```+ That order, not the other one. `git push && npm run verify` reads as a safe+ chain and is not one: it publishes first and objects afterwards, so a failing
  • llms.txt +17 −9

    https://filelayer.dev/llms.txt

    − Status: alpha / developer preview, version 0.26.0, Apache-2.0. Never deployed in production. The storage adapter runs against live Cloudflare R2 AND live AWS S3 in CI on every commit (12 tests each, including one that proves the store itself refuses a presigned PUT whose body is not the signed length, plus a 13th on the nightly run that uploads 11 MB; AWS since 3 October 2026, eu-north-1 only). The schema may change in any 0.x release. Requires Node >= 22.18. Install: `npm install @filelayer/cor− Freshness: this file describes version 0.26.0 and was written on 2026-10-11. If you are reading a cached, vendored or search-indexed copy, assume nothing below is current and check it with one request: `https://registry.npmjs.org/@filelayer/core/latest` returns the current version as JSON, and it cannot be stale. If that version is not 0.26.0, every figure below is from an earlier release and you should re-fetch https://filelayer.dev/llms.txt before relying on any of it. This warning is here bec− Every link below points at GitHub. If you are reading this out of an install you may have neither network nor browser, and you do not need them: the same files are on disk under `node_modules/@filelayer/core/` — `README.md`, `docs/QUICKSTART.md`, `docs/guides/`, `llms.txt` (this file), `openapi.json`, `schema.sql`, `SEMANTICS.md`, `MIGRATIONS.md`, `CHANGELOG.md`, `LICENSE`, `NOTICE`, plus `src/`, `test/`, `examples/` and `migrations/`. Three resolve as subpath imports: `@filelayer/core/llms.txt`− - Entry point: `import { Filelayer } from '@filelayer/core'`. Throwaway instance: `await Filelayer.quickstart({ baseUrl })` (PGlite + in-memory bytes, everything lost on exit). Real instance: `new Filelayer(pgPool, storage, { baseUrl })` where `storage` is one of three adapters — `new FsStorage('./data')` for bytes on a local disk (no bucket, no IAM user, survives a restart, no `presignGet` so redirect delivery is unavailable), `new S3Storage({ endpoint, bucket, region, accessKeyId, secretAccess− - [Expiring and revocable file links](https://github.com/filelayer/filelayer/blob/main/docs/guides/expiring-and-revocable-file-links.md): why a presigned URL cannot be revoked, the four things you can do instead, and the one query that is easy to write wrong — with the schema and the SQL to copy. Read this before designing any share feature on presigned URLs.− - [Private file uploads](https://github.com/filelayer/filelayer/blob/main/docs/guides/private-file-uploads.md): a presigned PUT does not limit the size of the body — a URL issued for a 200 KB avatar accepts three gigabytes — and the presigned POST policy that does. Plus the content-type trap, why the object key is not access control, and why the upload happening outside your transaction leaves orphans. NOTE: as of 0.10.0 Filelayer DOES do pre-authorized direct upload via `createUpload()`/`comple− - [Multi-tenant file access control](https://github.com/filelayer/filelayer/blob/main/docs/guides/multi-tenant-file-access.md): Row-Level Security filters which rows a session may READ and does not stop it WRITING a grant that joins two tenants — demonstrated, and refused by a composite foreign key that holds even against a role with BYPASSRLS. Plus why the bytes in your bucket are outside every policy you wrote.− - [Deleting files and orphaned objects](https://github.com/filelayer/filelayer/blob/main/docs/guides/deleting-files-and-orphaned-objects.md): delete the ROW first, because an orphaned object is a recoverable failure and a row pointing at nothing is not. Then the part nobody writes down: the sweeper that reclaims orphans deletes on the strength of an ABSENCE, and a partial listing is indistinguishable from one. Measured — the same short listing hides orphans in a storage-driven sweep and destroys− - **Three things are off by default**, each for its own reason: `allowDestructive` (deleting is not recoverable), `returnFileBytes` (a document returned by a tool is a document copied into a model's context, and `file_info` plus a share link is usually what the agent actually needed), and `exposeAuditTrail` -- because reading the trail is the one act this library does not record, so an agent could read an entire organisation's history and leave nothing behind. That gap is [LIMITATIONS](https://g+ Status: alpha / developer preview, version 0.33.0, Apache-2.0. Never deployed in production. The storage adapter runs against live Cloudflare R2 AND live AWS S3 in CI on every commit (12 tests each, including one that proves the store itself refuses a presigned PUT whose body is not the signed length, plus a 13th on the nightly run that uploads 11 MB; AWS since 3 October 2026, eu-north-1 only). The schema may change in any 0.x release. Requires Node >= 22.18. Install: `npm install @filelayer/cor+ Freshness: this file describes version 0.33.0 and was written on 2026-10-15. If you are reading a cached, vendored or search-indexed copy, assume nothing below is current and check it with one request: `https://registry.npmjs.org/@filelayer/core/latest` returns the current version as JSON, and it cannot be stale. If that version is not 0.33.0, every figure below is from an earlier release and you should re-fetch https://filelayer.dev/llms.txt before relying on any of it. This warning is here bec+ Every link below points at GitHub. If you are reading this out of an install you may have neither network nor browser, and you do not need them: the same files are on disk under `node_modules/@filelayer/core/` — `README.md`, `docs/QUICKSTART.md`, `docs/guides/`, `docs/CLI.md`, `docs/AGENT-SKILL.md`, `docs/DIRECT-UPLOAD.md`, `docs/MCP.md`, `llms.txt` (this file), `openapi.json`, `schema.sql`, `SEMANTICS.md`, `MIGRATIONS.md`, `CHANGELOG.md`, `LICENSE`, `NOTICE`, plus `src/`, `test/`, `examples/` a+ - One page per thing the README points at rather than explains: [docs/CLI.md](https://github.com/filelayer/filelayer/blob/main/docs/CLI.md) (`filelayer doctor`, and why it takes no credential as an argument), [docs/AGENT-SKILL.md](https://github.com/filelayer/filelayer/blob/main/docs/AGENT-SKILL.md), [docs/DIRECT-UPLOAD.md](https://github.com/filelayer/filelayer/blob/main/docs/DIRECT-UPLOAD.md) (why a presigned PUT does not bound the body) and [docs/MCP.md](https://github.com/filelayer/filelayer+ - Entry point: `import { Filelayer } from '@filelayer/core'`. Throwaway instance: `await Filelayer.quickstart({ baseUrl })` (PGlite + in-memory bytes, everything lost on exit). Real instance: `new Filelayer(pgPool, storage, { baseUrl })` where `storage` is one of three adapters — `new FsStorage('./data')` for bytes on a local disk (no bucket, no IAM user, survives a restart, no `presignGet` so redirect delivery is unavailable), `new S3Storage({ endpoint, bucket, region, accessKeyId, secretAccess+ - [How do I make a file link that expires — and that I can take back?](https://github.com/filelayer/filelayer/blob/main/docs/guides/expiring-and-revocable-file-links.md): why a presigned URL cannot be revoked, the four things you can do instead, and the one query that is easy to write wrong — with the schema and the SQL to copy. Read this before designing any share feature on presigned URLs.+ - [How do I let users upload files privately?](https://github.com/filelayer/filelayer/blob/main/docs/guides/private-file-uploads.md): a presigned PUT does not limit the size of the body — a URL issued for a 200 KB avatar accepts three gigabytes — and the presigned POST policy that does. Plus the content-type trap, why the object key is not access control, and why the upload happening outside your transaction leaves orphans. NOTE: as of 0.10.0 Filelayer DOES do pre-authorized direct upload via `c+ - [How do I do multi-tenant file access control?](https://github.com/filelayer/filelayer/blob/main/docs/guides/multi-tenant-file-access.md): Row-Level Security filters which rows a session may READ and does not stop it WRITING a grant that joins two tenants — demonstrated, and refused by a composite foreign key that holds even against a role with BYPASSRLS. Plus why the bytes in your bucket are outside every policy you wrote.+ - [How do I delete a file without leaving orphaned objects in S3?](https://github.com/filelayer/filelayer/blob/main/docs/guides/deleting-files-and-orphaned-objects.md): delete the ROW first, because an orphaned object is a recoverable failure and a row pointing at nothing is not. Then the part nobody writes down: the sweeper that reclaims orphans deletes on the strength of an ABSENCE, and a partial listing is indistinguishable from one. Measured — the same short listing hides orphans in a storag+ - [Should I redirect to a presigned URL or proxy private files myself?](https://github.com/filelayer/filelayer/blob/main/docs/guides/serving-private-files.md): a redirect's revocation window is the URL's remaining TTL, not a moment, and no operation shortens it; proxying makes it one request. Then the bill proxying sends: `Range` is yours, and four of the five ranges that look like they deserve a 416 must be answered 200 instead. The whole table, measured, including the multi-range case whose na+ - [How do I revoke a share after the recipient has shared it onwards?](https://github.com/filelayer/filelayer/blob/main/docs/guides/revoking-delegated-access.md): whether you CAN revoke onward access is decided when you store the share, not when you revoke it — a re-share that writes an independent row cannot be undone later. One column (`parent_grant_id`) and one predicate (liveness over the whole ancestor chain), plus the four dimensions of attenuation. Measured: capability attenuation alone l+ - [How do I prove who accessed a file?](https://github.com/filelayer/filelayer/blob/main/docs/guides/tamper-evident-file-audit.md): a hash chain detects three of the four things that can be done to a log and NOT the fourth — truncation of the most recent events — which is structural rather than an oversight, so the head has to be pinned outside the database. Measured, including the defect almost every implementation ships: a SELECT-then-INSERT append forks under concurrent writers, so an honest + - [What background jobs does a file feature need, and did they run?](https://github.com/filelayer/filelayer/blob/main/docs/guides/background-jobs-for-user-files.md): four jobs, and the problem is that every one of them produces an ABSENCE as its output, so a job that silently stopped running looks exactly like a job with nothing to do. Measured: a collector that deletes a file the user just finished uploading, and the one-statement claim that closes the race; six weeks of dry runs that read as h+ - **The audit read takes a TIME WINDOW**, since 0.31.0: `{ since, until }` alongside `decision`, `action`, `file`, `user` and `limit`, on both `fl.orgs.audit()` and `fl.auditLog()`. Half-open — `[since, until)` — so consecutive windows tile without double-counting the row that lands exactly on a boundary, which is what a monthly export needs. "The last ninety days" previously had to be done by pulling rows under a guessed `limit` and filtering in JavaScript.+ - **Its absence was the sharp kind.** `trimAuditChain` has taken a `before` instant since 0.16.0, so this API could DELETE history by time and could not READ it by time. Found by a graded evaluation of the Agent Skill rather than by review: the answer written with the skill invented a `since` option and flagged it as a guess, which was a good guess at an API that should have existed.+ - **`auditReader(db)` reads the trail with NO storage adapter**, since 0.30.0. A compliance dashboard, a report job or an operator script does not hold bytes and should not have to construct something that claims it can: the three audit reads reference storage zero times and `PostgresStore` takes a connection and nothing else, so the old requirement came from where the methods lived rather than from what they do. `auditReader(pool)` returns `{ log, verify, checkpoints }` taking the same `Princip+ - **Three things are off by default**, each for its own reason: `allowDestructive` (deleting is not recoverable), `returnFileBytes` (a document returned by a tool is a document copied into a model's context, and `file_info` plus a share link is usually what the agent actually needed), and `exposeAuditTrail` -- because the trail is an organisation's whole history and a tool that returns it copies every file name, actor and denial into the model's context. Reading the trail is itself audited since

  • server.json 1 field changed
    version
    0.32.0 0.33.0

  • server.json 1 field changed
    version
    0.31.0 0.32.0

  • server.json 1 field changed
    version
    0.30.0 0.31.0
  • server.json 1 field changed
    version
    0.29.0 0.30.0
  • server.json 1 field changed
    version
    0.28.0 0.29.0

  • server.json 1 field changed
    version
    0.27.0 0.28.0
  • server.json 1 field changed
    version
    0.26.0 0.27.0