Repository navigation
v0.16.0
A private deployment now signs its readers in itself and opens each folder
only to the Google groups allowed to read it. Cloudflare Access is no longer
part of the platform. See
ADR-038,
ADR-039
and
ADR-040,
which supersede ADR-004.
Breaking
- A deployment with any private environment must be served through the
platform's Worker:ctcdocs-sync validatefails untilsite.config.json
hassignIn,wrangler.jsoncdeploys the gate, and
wrangler.directory.jsoncexists. Move off Cloudflare Access as the
upgrade note below describes; a rollback refuses any version deployed
before the gate. .ctcdocs/must be ignored by Git on every deployment, and a private one
may not havepublic/_redirects.auth,pagefindandassetsare reserved addresses; a corpus already
holding one fails validation until the source is renamed in Drive.- The smoke environments take
CTCDOCS_MACHINE_KEY, and the rollback caller
passes arestore_older_secretsinput. - The manifest gains
publishedReaders,publishedChainandreadersHeld
when anaccesssection exists; older manifests still load.
Changed
- A deployment with any private environment is served through the
platform's Worker,@ctcstack/ctcdocs/worker, which runs before every
asset. Readers sign in with Google — an OAuth client of the organization's
own, with an Internal consent screen — and are identified by their Google
user ID; a session lasts twelve hours and is honored only while the directory
lists the reader as active. Every file is served only to the readers its
access class names, over HTTPS only, with the response headers_headers
used to set,Strict-Transport-Securityand a Content Security Policy; the
Worker follows no redirect from the asset store. The Worker uses
web-standard APIs only, with Cloudflare confined to its entry points. ctcdocs-sync validaterequires of such a deployment asignInsection, a
wrangler.jsoncthat deploys the platform's Worker withrun_worker_first,
the access map alias, aKB_STATEnamespace and aGOOGLE_CLIENT_ID
variable in each environment, and awrangler.directory.jsoncwith no route;
it refusespublic/_redirectsthere. Every project must keep
.ctcdocs/out of Git.ctcdocs-access-smokeproves admission with a machine key
(CTCDOCS_MACHINE_KEY), and also sends an Access service token while one is
configured; a redirect to sign-in counts as a denial. It picks a document
every member may read from the configuration and the manifest, reads the
project's.envwithout overriding the environment, and--anonymousruns
on a project that has not synchronized yet. The Playwright access suite
takes either credential.- The deployment workflow runs the denial suite, deploys the directory Worker
before the site, with retries, and tags every version of a gated deployment
ctcdocs-gate-v1. The rollback workflow refuses a version without that tag,
passes the smoke key to its boundary check, and refuses a version older than
a change to the Worker's secrets — which a rollback restores — unless called
withrestore_older_secrets. The reusable CI and sync workflows run the
denial suite too. auth,pagefindandassetsare reserved addresses.
Added
signIn.workspaceDomainsinsite.config.json: the organization's domains,
whose accounts may sign in.- A directory Worker,
@ctcstack/ctcdocs/worker/directory, which reads the
members of every group the rules name, and the users of each domain, every
ten minutes through the Directory and Groups Settings APIs, and writes one
snapshot to KV. Groups are pinned to their Google IDs. A group that is
nested, holds the organization, lets people join themselves, admits
outsiders, was recreated, or whose members or settings cannot be read admits
no one, and the rest is still written. A failure that may pass stops the run
and keeps the last snapshot, and so does a result that loses more than a
fifth, and at least five, of the users or of a group's members beyond those
listed as suspended or archived — unless an operator sets
directory-accept-next. With no snapshot no session is admitted; with one
older than two hours a reader reads only what every member reads. Failures
are logged by stage and status, never by address. - Machine keys for the smoke test and for agents:
ctcdocs-machine-key
issues one, valid for at most 90 days, and prints the record the
environment'smachine-keyslist in KV keeps — a hash, a name, an owner,
groups and an expiry. A key is never an administrator. /auth/sign-in,/auth/callback,/auth/sign-outand/auth/signed-out;
Sign out in the header and the mobile menu, which also clears the
browser's cache of the site; a page for someone the directory does not list,
who gets no session;/_kb/classes, which lists a reader's search bundles;
and/_kb/status, where an administrator reads the snapshot's age and
counts.ctcdocs-verify-gate, the denial suite: the real gate in front of the real
build, asked for every file as nobody, a members-only key, a key per
restricted class and an administrator, without a network.project-probe.yml, a reusable workflow that asks production anonymously
for a page, a Markdown file, the agent index and the search runtime, on the
caller's schedule, and fails when any is admitted.- An optional
accesssection insite.config.jsonnames who may read each
Drive folder: admin groups, and per folder the Google groups that may read
it, or"*"for every member. A document's readers are the groups every
rule on its folder chain names; a folder with no rule is closed to all but
the admins. - A move in Drive never widens a document's readers by itself. A rule change
takes effect at once; a document moved to a place whose rules would let more
people read it is published to the readers both places allow, listed under
Fix now ("More people would read the document than before") and shown by
title alone on its folder's page, until a rule on its new chain is added or
changed. The manifest recordspublishedReaders,publishedChainand, while
a move waits,readersHeld. - The content health page and the sync job summary list folders closed for
want of a rule, rules whose label no longer matches the folder, rules for a
folder the corpus does not have, and groups a rule names that the rule above
does not. With anaccesssection, the content health page is readable by
administrators only. - The
llms.txtindexes describe a document only in an index of its own
access class and list the rest by title and address, and a home page folder
card takes its description only from a document every member may read. - Every build writes an access map,
.ctcdocs/access-map.json, outside
dist: the access class of every built file, which the Worker is bundled
with. Once rules exist, a file the map cannot place fails the build, and so
does a listing that shows an image of a document with fewer readers. - Search is split by access class: Starlight's own Pagefind run is off, and
the platform writes/pagefind/for every member and/pagefind-<class>/
for each other class. The search box and the 404 page merge the bundles the
Worker lists for the reader at/_kb/classes, and search/pagefind/alone
where that route does not answer.ctcdocs-verify-searchsearches each
document in its own class's bundle and checks that no narrower document is
found in/pagefind/. - Once rules exist, the build fails when a file readable by a wider class
repeats a run of eight words found only in documents of a narrower class —
an index description, a listing excerpt, a quoted heading. Titles, folder
names, navigation and addresses are not counted, line breaks and alt text
stay inside their paragraph, and the failure names the file, the document
and the word offset, never the words.
Upgrade note
A private deployment fails validation on this release until it is moved off
Cloudflare Access. Follow
Cloudflare setup,
which keeps Access in front until the Worker is proven. In short:
- In Google: per environment, an OAuth client with an Internal consent screen
and its redirect URI; a directory-reader service account with a JSON key per
environment, a custom admin role granting Users → Read and Groups → Read
assigned by a super administrator, the Admin SDK and Groups Settings APIs
enabled; an administrators group. See
Google Workspace setup. - In Cloudflare: Workers Paid; Always Use HTTPS; a KV namespace per
environment; the secretsGOOGLE_CLIENT_SECRETandSESSION_SECRETon the
site Worker andDIRECTORY_KEYon the directory Worker; the smoke key's
record inmachine-keys; a deploy token without Workers KV permissions. - In the project:
signInand, if folders are to be closed,accessin
site.config.json;wrangler.jsoncand a newwrangler.directory.jsonc
following the fixture project;.ctcdocs/and.dev.varsin.gitignore;
nopublic/_redirects;deploy:dry-runcovering both Workers. - In GitHub:
CTCDOCS_MACHINE_KEYin each smoke environment; the rollback
caller passing arestore_older_secretsinput; a caller of
project-probe.ymlon a schedule; and, once Access is gone,
CF_ACCESS_CLIENT_IDandCF_ACCESS_CLIENT_SECRETremoved. - A public deployment needs only
.ctcdocs/in.gitignore.