The budget is one number in config/settings.yml. The deadlines that have to
cover it are in four other files, none of which anybody edits while thinking
about shutdown - so raising the budget passes every test, deploys, and has the
cleanup callbacks killed on the next release.
Two checks share one arithmetic and one five-second margin.
shutdown-budget-overruns-grace compares preStop + drain + server + cleanup
against terminationGracePeriodSeconds in the shipped manifest. Those two files
are not merely adjacent examples: scripts/k8s/prerun.sh builds the
settings-admin ConfigMap out of config/settings.yml and the Deployment mounts
it, so the manifest deploys that file.
docker-stop-cuts-shutdown-short covers the three ways this container is
stopped: `docker stop` in the release workflow, the same in the Makefile, and
stop_grace_period on a compose service that runs this repository's own image. A
service running a database is not this process and is left alone. The duration
is parsed rather than scanned for digits - compose accepts 1m30s, and reading
the first number out of it would call ninety seconds one.
All three spellings of the deadline are read: --timeout, the deprecated --time,
and the short -t. A deadline the check cannot read is reported as no deadline at
all, so recognising only one of them would call a correct command broken and
send whoever fixed it towards the spelling docker is retiring. The message
quotes the flag back in the spelling it was written in, for the same reason:
suggesting a flag the line does not use is how a tool teaches people to
disbelieve it.
What neither covers is `docker rm -f`, which has no deadline to compare
against: it is SIGKILL by definition. That gap is deliberate, and it is why the
previous commit changed the one place that used it on a container that might
still be running.
Both report at two levels. A budget that already overruns is an ERROR; one that
fits with nothing to spare is a WARN, because it works today and failing the
build on a working configuration is how a project teaches people to ignore its
warnings. The two are exclusive: an overrun satisfies the headroom condition as
well, and an ERROR that always drags a duplicate WARN behind it teaches the same
lesson.
preStop is in the sum although the shipped manifest has no hook. That is the
point - a hook added later is spent before the process is told anything, and a
self-check that could not see it would understate the real budget by however
long somebody set it to, which is worse than not checking. A hook whose duration
cannot be read is reported rather than counted as zero.
The fallbacks for fields the settings file leaves out are read from the
constants in the scanned tree, not copied here; if they are renamed the run
stops instead of going quiet with the wrong numbers.
The wording differs by audience on purpose. At run time this is somebody else's
deployment under constraints the process cannot see, so the log states a
minimum. These checks read files this repository owns, where there is standing
to ask for headroom, so they name a target.
The table in AGENTS.md is relisted while it is being touched: the two new
checks, plus datascope-route-unguarded, which has been missing since it was
added. The hard-coded count is gone - it said seven and there were ten, which is
what a written-down count does. AGENTS.md and docs/contract.md both sent readers
to `go run ./tools/checksilent -h` for the list of checks; that prints
command-line flags and has never printed a check, so both now point at
runChecks.
The yaml parser moves from an indirect requirement to a direct one - it was
already in the module graph - and tidy drops four go.sum lines left over from
two older releases of core.
The comment claimed bindings were collected as the body was walked so that a
registration only saw definitions above it. That is the single-pass design this
started as. The code does two passes - one to collect, one to report - and a
registration therefore sees every binding in the function.
That is the point rather than an accident: a `.Use` written below a route is
still part of the chain, because the chain is assembled before anything is
served. The price is that a name reused for two different things in one
function resolves to the last assignment, which the comment now says instead of
promising an ordering the code does not keep.
GetPermissionFromContext cannot fail. When no middleware put a *DataPermission
in the context it returns the zero value, and the zero value's DataScope is the
empty string - which is not one of the five scopes Permission recognises, so it
takes the default branch and fails closed. The query is handed `1 = 0` and
matches nothing.
The endpoint then reports "not found" or "no permission" for rows that plainly
exist, and only where enabledp is true. With data permissions off - the
repository default - Permission returns the query untouched and the missing
middleware costs nothing at all. A test suite and a CI that run on the default
cannot see it.
That is what happened to /api/v1/getinfo: it read the permission on a group
carrying only the JWT middleware, so every login on a deployment with data
permissions enabled ended in a 401 from the endpoint the browser calls
immediately after signing in, and went back to the login page. Three /sys-api
routes had the same shape.
The check matches a handler to the group it is registered on, through the AST
rather than through the text - a scratch grep for the same thing reported four
false positives from a comment that happened to contain the function's name,
and before that, a dozen from matching handler names across packages. Handlers
are keyed by package, type and method, so two SysUser types are two handlers.
Subgroups inherit their parent's chain, as gin does, and a `.Use` written below
a registration still counts, because the chain is assembled before anything is
served.
Either half is a fix and the message says both, because which one is right
depends on the route. A handler reading other people's rows wants the
middleware. A handler reading the caller's own row - id from the token - wants
no scope at all: DataScopeSelf matches on create_by, so scoping a self-read
rejects every user who did not create their own account. Reporting only "add
the middleware" would have turned /getinfo from broken into worse.
Five tests: the mistake, both fixes, subgroup inheritance, and a same-named
handler in another package. TestThisRepositoryIsClean covers the real tree, and
it is what fails on the commit before this one - four findings, all real.
The note explained the empty-shim summary as the expected state "until the
contract packages are lowered into core". They are lowered, and the shims
exist - so a count of zero now means they stopped being aliases, or stopped
being here, which is the interesting case rather than the ordinary one.
Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
The contract-shim-alias message built its suggested line from path.Base of
the import path, so it told the author to write
type ControlBy = models.ControlBy
in a file whose import is `contractmodels "…/sdk/contract/models"`. Every
shim in this repository aliases that import, so the suggestion never
compiled as written - in the one message whose whole job is to be pasted in.
qualifiedType already read the in-source identifier to resolve the import;
it now returns it.
Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
Widening menu-sort-overflow to see a contract MenuSpec made it fire on
app/admin/service/seed_test.go, on the case that asserts SeedMenus rejects
a sort of 900. The check was reading the proof that it works as a defect.
That is not specific to this one guard: menu-sort-overflow,
config-value-truncation, menu-id-collision and modeltime-mix are all about
a value that reaches a real database through a migration, a test fixture
reaches none, and every one of those guards needs a test that writes the
value it rejects. Skip _test.go in all four.
The two import and alias checks keep scanning tests - those are about the
dependency graph, where a test file's import is as real as any other, and
TestContractImportBoundaryCoversTestFiles already pins that.
Both directions are covered: a fixture is ignored, a real seed is still
reported.
Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
The sort-overflow check recognised a SysMenu literal from this repository's
model packages and nothing else. An application installed from outside cannot
reach that type - it describes the same row as a seed.MenuSpec and hands it to
the host's Seeder - so the check went quiet for exactly the author furthest
from the schema it protects.
Not hypothetical: this repository's own reference application shipped a Sort
of 200, past the tinyint sys_menu.sort is built as, and this check passed it.
sqlite ignores the width, so it would have surfaced first on a real install,
as Error 1264 partway through a migration with everything after it unapplied.
The check still cannot see an application in the module cache; that half is
the Seeder's runtime validation. This closes the half that is in the tree.
Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
A shim of a core contract type written as `type X pkg.Y` instead of
`type X = pkg.Y` keeps the fields and drops the method set, so anything
embedding it stops satisfying the interfaces it satisfied before.
The compiler catches that only where the method set is actually
exercised. This repository exercises some of the contract types through
an interface and some not at all, so the ones it does not exercise
compile here and break in a fork or a third-party application - which
is the half nobody is watching.
The trigger is the right-hand side of the declaration rather than a
list of package names, so it covers whatever the lowering ends up
shaping without a list to keep in step. Until the contract packages
land in core there is nothing here to guard, and the summary says so
rather than letting the silence read as a pass.
Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
Six checks, five at ERROR and one - the cross-repository menu-name comparison -
at WARN, because it can only match by regular expression across two modules and
a false positive that fails CI teaches people to silence the tool.
The summary names which contract roots were actually scanned: core/ is a
separate module with no directory here, and a check that quietly covers less
than it claims is worse than no check.