Three things, all in the path between this repository and the running image.
Closes#225, #224 and #223.
1. THE PRODUCTION SITEMAP CARRIED NO LASTMOD AT ALL. Dates come from git
history, and the image build cannot see git: .dockerignore excludes .git and
node:alpine has no git binary. prerender.js read the failure into an empty
catch commented "git unavailable or file untracked", so all 18 URLs came out
undated while the build printed a success line. Local builds looked perfect,
which is why nobody caught it.
release.sh now computes the map where git exists, passes it as the
SITEMAP_LASTMOD build arg, and then asks the built image whether its sitemap
has dates, refusing to publish one that does not. prerender prints the count
on every run, so "18 URLs, 0 dated" can never again read as success. The
route-to-source map moved into scripts/lib/routes.js, where a service page
now also counts its own content file, so editing one page's copy moves that
page's date and no other.
Proven: an image built with the arg carries 18 lastmod entries; a build with
git deliberately unreadable and no arg reports "18 URLs, 0 carrying a
lastmod" and warns.
2. THE DOCKER BUILD CONTEXT CARRIED CLIENT MATERIAL AND LIVE SECRETS. .drop/,
zoho.md (the reCAPTCHA secret and the Zoho tokens), Levi.md and two 30 MB
zips were all sent to the daemon on every build, along with four agent
workspaces. The final image copies only built output, so none of it ever
shipped, but one careless COPY would have changed that. Proven by listing the
context from inside a throwaway image: before, all of it; after, none of it.
3. UNTRACKED FILES PASSED SILENTLY. docker build packs the working tree, so an
untracked module the code imports produces an image that works and a tag that
cannot rebuild it. release.sh now refuses while untracked files are present,
and pre-commit's note counts them too.
#223 also claimed post-commit hides a refused push. It does not: it printed
"push was refused. The commit is safe locally and the branch is now ahead."
during this batch. The issue was corrected on the tracker rather than acted on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This checkout already had a pre-push in .git/hooks. Setting core.hooksPath
redirects every hook to .githooks/, so activating the template's hooks would
have silently stopped it running — which is the exact failure the versioned
hooks directory exists to prevent, applied to a hook.
Kept its two working-tree checks: a push that leaves edits behind is how docs
end up one commit adrift of the code they describe.
Dropped its third. It refused whenever the branch was ahead of its remote —
the precondition for pushing at all — so it fired on every real push and its
only advice was to re-run with --no-verify. A guard that can never pass teaches
people to bypass the two beside it that can. It did exactly that on the push of
the adoption commits.
Both remaining checks proven to refuse a dirty tree and pass a clean one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ten scripts from ~/.openclaw/Projects/Template, taken one at a time and
configured against this deployment rather than copied wholesale.
Configured, not just copied:
- check-env.sh SPEC written from what server/index.js actually reads — 24
variables, each with the consequence of getting it wrong
- secrets.sh plus this project's own shapes: a bare 60+ hex run, which is
how the Zoho WebToLead tokens leaked into four commits, and a
reCAPTCHA key shape as NOTED rather than a failure, because the
site key and the secret key are indistinguishable by shape
- status.sh nebula / qn-website-dev
- healthcheck.sh /api/health, asserting 200 AND "status":"ok" AND "db":"ok".
The template probed /healthz, which does not exist here
- preflight.sh https://qn.isnull.dev, no --auth — there are no accounts
- verify.sh GUARD_DIR=scripts/verify.d, since this project has no test
runner and no typecheck for it to detect
- backup.sh ENGINE block replaced for SQLite: better-sqlite3's online
.backup() inside the container, verified with PRAGMA
integrity_check before anything is renamed into place
- restore-check.sh rewritten rather than configured — the template's is
pg_restore/psql end to end with no seam. Replays the dump from
SQL into a scratch database and times it
Three guards in scripts/verify.d, because verify.sh would otherwise detect
nothing and exit 2: the build, the tracked-tree secret scan, and a check that
every document carries a valid Status, Governs and Review trigger.
Every guard was proven to fail before being trusted, per GUARDS.md rule 1:
healthcheck against a 200 that is not this app, secrets against the real
historical leak replayed out of 033bdf6, doc-headers against both a missing
Review trigger and the Status word "Historical", restore-check against a
truncated dump, an empty database and a raised row floor.
pre-commit is ADAPTED, not the template's. That one runs `npx tsc --noEmit` and
`npx vitest run`; this project has neither, so unchanged it would refuse every
commit. It runs the secret scan and `npm run build`. Hooks are not activated by
this commit — `git config core.hooksPath .githooks` is a separate, per-clone act.
package.json: adds `verify`, and corrects the version to 0.9.3. It said 0.8.3
while the last four commits said batch 0.9.0 through 0.9.3 — the second drift of
the phase-versioning rule, which is retired in the following commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>