The server's golden helpers (the comparer, the -update flag and the
normaliser that turns a fixture's ids into names) move into
internal/testkit/golden, so any package's tests can compare output with a
file under testdata. Ordinals is new: it numbers the UUIDs and UTC times
an act makes by first appearance, for the ids a fixture cannot name.
Normalizer.Add replaces the one place a server test appended to the
normaliser's unexported pairs.
Eleven server test files use the package; the -update flag keeps its
name. docs/testing.md gains the kit row and a bullet, and the kit's import
guard and the dead-code list know the package.
No output changes. No production file changes, and no file under
internal/server/testdata is touched; every server golden test passes
without -update and git status is clean under testdata afterwards. The
names and results of ./internal/testkit/... and ./internal/server are
equal at the parent and at this commit (1452 and 1458 tests and
subtests), apart from the five new tests and the import guard's new
subtest for the package. No assertion changes, so no assertion map.
13 breaks: 12 caught by the new tests, and the one that renames the
normaliser's <today> pair is caught by the golden test at the parent and
at this commit.
Unprobed: that a name outranks a pair in the server's goldens (their ids
are UUIDs and their pairs a date and the suffix, which never start at one
position, so the order is invisible there; the kit test probes it); the
server copy of the comparer at the parent (a break that makes it pass
everything cannot fail the goldens it compares); a migration test that
defines its own update flag (a documented rule, not code).
The organization page named each row with its own read: a product per
delivery, a ladder and a tier per rung, an actor, a ladder and a tier
list per tier change, an actor per entitlement change, and a ladder list
per product in the Issue grant advisory. The page now makes one resolver
after the organization read and every section names its rows through it,
so each kind of name is one statement for the page (#10): the panel reads
every pool's positions first and wants their products, ladders and
tiers; Tier changes fold and page the rows before naming them, so only
the window's rows are named; Entitlement changes want the page's actors;
the advisory reads the tiers of the held ladders in one statement
(productsOnLadders) and flags the products on them. The grants ledger
takes the page's resolver in place of its own. The owner is still one
GetPersonByID read, as before. GetTier is deleted with its last caller,
and the resolver functions that now have a production caller leave the
test-only list.
One current-shape rule now names tiers here: a dash for a row with no
rank, the product at that rank on the ladder as it is now, or "rank N"
when no tier holds the rank. It replaces the page's own closure and its
two caches.
Accepted difference, named: a failed batch read now fails every id of its
kind together (every delivery's product name, every ladder name, every
rank, every actor), where one id could fail while the others resolved.
Only an injected failure reaches it. The failure table's row for a failed
product ladder read is NARROWER for it: the advisory's read is one batch
over the held ladders, so the partial failure has no row.
How it was shown to change no output: every golden under
testdata/org_enrollment is byte-identical, and the tests of
internal/server and internal/entitlements give the same 1588 names and
results at the parent and here (1587 pass, 1 skip). The organization
page's read-count row goes from 8 per row to 0. The failure table keeps
its names and moves the method each row fails: actors to
ListPersonNamesByIDs, products to ListProductNamesByIDs, ladders to
ListPlanLadderNamesByIDs, and rank, tier labels and the product ladder
row to ListTiersWithProductsByLadderIDs; the owner row still fails
GetPersonByID. The operator walkthroughs give 22 equal names, results and
skips at the parent and here. loadOrgEnrollmentData is re-pinned at 63,
the score golangci-lint printed with a placeholder pin.
Breaks: 76 records, 49 names, 51 run per side (26 on both sides, 25
old-only, 25 new-only); all 51 CAUGHT at the parent and all 51 CAUGHT
here.
Unprobed: which ids each section wants (only the page's rows; a wider
want changes the batch's id list, not the statement count or the output);
the sorted order of the batch's ids; the guard that skips the advisory's
read when no ladder is held (an empty batch answers no rows, the same
flags); the removed nil check on the identity querier for entitlement
change actors (every test handler has one).
The grants page and the organization page's ledger drew each row with
reads of their own: the organization, the product, the granting person
and the floor product, once per row or per distinct id. They now name
every id on the request's resolver (#10). wantGrantNames wants what the
surface shows, buildGrantViewModels reads nothing per row, and each name keeps
its fallback: no organization name, a blank product, "System" for the
person, the revoke body's generic sentence for the floor. The grants
page wants the organization of each row and the ledger the floor of a
row whose revocation the floor would follow, so neither reads what it
does not show.
The ledger windows the rows of its tab before it names them. With a
search typed it first reads the product names of the whole tab in one
batch, because the search matches product names before the page is cut,
as before. That is the one read beyond the rendered page, and it is one
statement whatever the number of rows. OnActiveTab's switch moved to
onActiveTab, which the ledger applies to rows before they are drawn.
How the commit was shown to change no output:
- internal/server and internal/entitlements give the same 1578 test and
subtest names and results at the parent and at this commit (1577 pass,
1 skip), and every file under testdata/grants_listing/ and
testdata/org_enrollment/ is unchanged.
- Four read-count values changed, as intended: grants 5 to 0 per row,
grants-ledger 2 to 0, grants-ledger with a search 2 to 0, and the
organization page 9 to 8 (the ledger's product read left it).
- 13 breaks, each run on both sides and caught on both: the grants
page's organization, the granting person, the row's product, the
revoke body's floor, a replacing grant outside the rows, a superseded
row's replacement, the Active tab's two rules, the ledger search by
reason and by product, the ledger's paging, and the two per-row read
counts.
- The gocognit pin of buildGrantViewModels went from 84 to 33, the score
golangci-lint measured on the committed code. The six resolver
functions this commit first calls from production left the test-only
list.
Test change: wantOwnerID looked for one link to the owner on the whole
page. The rich fixture's owner also granted the ledger's grants, and the
failing person read it injects used to blank that Granted by cell. The
ledger now names granters in one batch the injected failure does not
reach, so the cell links the owner as well. The check now looks at the
Owner field's link. NARROWER than before: the page-wide check also held
that a failed person read leaves the ledger's Granted by cells on their
fallback, and that fact is no longer asserted here.
NARROWER than before: a name read that fails now fails for every id of
its kind on the page, where a per-row read failed for its own row alone.
The test doubles fail a method by name, and no double fails the batch
reads for these handlers, so that behaviour is not probed here.
Unprobed:
- The ledger's Granted by cells on their fallback when a person read
fails: the injected failure fails GetPersonByID alone, the ledger names
granters through the batch read, and no double fails that batch for
these handlers.
- A failed batch read on these two pages (above). How operator pages
show a failed read is #207's.
- The lineage label's branch that names a replacing grant by its own
loaded row: it equals the row's product on every row the console
writes, because an extension carries the product of the grant it
extends. A later section adds a test that reaches it.
- A grant with no organization: the old code skipped the floor read for
it, and the resolver answers an empty id as unknown without a read; no
fixture holds such a grant.
- The old per-person and per-product memos: output-equivalent, since the
resolver reads each id once.
The set's History section read each change's actor person with one
statement per row. It now wants every row's actor on the request's name
resolver and builds each row from it, so the section reads the names of
all its actors in one statement however many changes the page lists. An
absent identity querier leaves the actor unnamed as before; the resolver
reads nothing through a nil querier.
Narrower: a name read that failed for one actor alone, while the others
succeeded, can no longer happen; a failed read leaves every actor on the
page unnamed, as one failed read left its own row. The error is still
discarded and no log line is added.
Shown to change no output: 9 breaks, 4 written for the old code and 5 for
the new, each caught by the test it names (the person's name, a failed
read naming nobody, the absent identity querier, and the read-count row,
whose per-row count goes from 1 to 0; the new side adds a break that
drops the wanting, caught by the same row). The names and results of
internal/server (1398 results) and of internal/entitlements (180) are
identical at the parent and at this commit. The walkthroughs give the
same 22 results at the parent and at this commit, and the screens match
the baseline apart from the Stripe integration page, whose size changes
on every run. The set-history test's failed-read case keeps its name and
fails the read of people's names in place of the read of one person.
Unprobed: a name read that fails for one actor alone, because the new
code reads every actor in one statement, which cannot fail for one actor
alone, so no test of this commit can produce it; the resolver's own
behaviour (one read per kind, ids sorted, empty ids skipped), which the
resolver's own tests hold.
The accounts, subscriptions, invoices and payments lists, and the lookup's
invoice candidates, read one organization per row. Each now wants its
rows' organizations from the request's name resolver before the row loop,
which reads them in one query, and the loop looks each row's organization
up in the result. A page with no rows reads nothing. A failed read names
no row, so the lists show the empty name and the lookup shows the
organization's id, as a row whose own read failed did. The per-row count
of the four lists and of the lookup in
TestOperatorPagesReadAFixedNumberOfTimes goes from 1 to 0. The resolver's
newOperatorNames, both names methods, WantOrgs, Org, readOrgs and the
batch's want, lookup and flush now have a production caller, so their
test-only dead-code entries are removed.
Shown to change no output: 36 breaks of the organization reads, 16 run on
the parent and 21 on this commit (one is run on both), each caught by the
test that pins it. The eight golden files of the four lists are unchanged
and no -update ran. The names and results of internal/server and
internal/testkit (1435 results) are identical at the parent and at this
commit. The walkthroughs match the baseline (22 of 22) and the screens match
it apart from the Stripe integration page, whose size changes on every run.
The lint snapshot differs from the previous one by nothing.
Narrower than before: a failure of one row's organization read alone
cannot be shown any more, since one query cannot fail for one row.
Unprobed: the billing lists' ok test, because their fallback is the empty
name, which is also what the resolver answers when not ok; a missing
organization querier, which the resolver answers as unknown and which no
test of these pages sets.
The accounts, subscriptions, invoices and payments lists read one Stripe
mapping per row. Each loader now reads its page's mappings in one query
before the row loop (ListCustomerMappingsByBillingAccountIDs,
ListSubscriptionMappingsBySubscriptionIDs, ListInvoiceMappingsByInvoiceIDs,
ListPaymentMappingsByPaymentIDs) and the loop looks each row up in the
result. A page with no rows reads nothing, and the payments list still
reads nothing without a Stripe querier. A failed batch keys nothing, so
every row shows not mapped, no Stripe id and no badge, as a row whose own
read failed did. GetPaymentMappingByPaymentID had no other caller and is
deleted. The per-row count of the four lists in
TestOperatorPagesReadAFixedNumberOfTimes goes from 2 to 1 (the
organization name is still read per row). The name resolver's indexBy
now has a production caller, so its test-only dead-code entry is removed.
failingMappingsStripeQ overrides the four batch reads in place of the four
single-row reads; its subtests are unchanged.
Shown to change no output: 39 breaks of the mapping reads, 15 run on the
parent and 24 on this commit, each caught by the test that pins it. The
eight golden files of the four lists are unchanged and no -update ran. The
names and results of cmd, internal/fulfillment,
internal/integrations/stripe, internal/server and internal/testkit (1758
results) are identical at the parent and at this commit. The walkthroughs
match the baseline (22 of 22) and the screens match it apart from the Stripe
integration page, whose size changes on every run. The lint snapshot differs
from the previous one by nothing.
Narrower than before: a failure of one row's mapping read alone cannot be
shown any more, since one query cannot fail for one row.
Unprobed: the payments list's empty page, because its batch is guarded on
the Stripe querier and the test that checks empty pages sets none; the lines
that fill the Stripe id, the sync state and the badge, which this commit
does not edit and the golden pins; the demo seed writes no Stripe mapping,
so the screens show only the unmapped branch; the payments list's guard on a
missing Stripe querier, because no test loads a payments page that has rows
and no querier (the start command sets the querier whether or not a Stripe
key is configured; a page without one would show every row as not mapped).
Five sqlc queries read the names one operator page shows in one statement
each: people, organizations (with their type), products, plan ladders, and
the tiers of several ladders with their products. operatorNames collects the
ids a page wants, reads each kind once on the first lookup, and answers
(name, ok) so a caller keeps the fallback text it shows today for an id with
no row or a failed read. tierLabel is the one current-shape rule for a rank
on a ladder.
Nothing calls the resolver from production yet, so no page's output, status,
header, redirect, write or log line changes; the functions only tests reach
are listed in the test-only deadcode file until a page adopts them. Shown by:
the generated Querier rows match the single-row reads they batch
(ListTiersWithProductsByLadderIDsRow equals ListTiersByLadderWithProductsRow),
sqlc-generate run twice leaves no diff, and the packages the commit touches
pass on a reset test database.
New tests: TestOperatorNamesReadEachKindOnce, TestOperatorNamesAnswerNotOK,
TestOperatorNamesTiers, TestOperatorNamesFloor, TestOperatorNamesReadTheDatabase.
25 breaks, each caught by the new tests. Unprobed: the ORDER BY of the tier
batch query (callers index by map), the per-id narrowing (one statement cannot
fail for one id), every caller's fallback text (each adopting page probes it),
and that the resolver logs nothing (it holds no logger).
member_plans.go holds the handler, the plan view models, the plans page and
the cancel prediction; member_plan_moves.go the switch, keep and cancel
handlers; member_entitlements.go the entitlements panel; member_addons.go the
add-ons page; price_text.go the price renderers. Every declaration moves with
its comment, in its old order. The two section banners that divided the old
file are not carried over. Comments that named member_products.go name the
file that now holds what they cite.
No output changes. The multiset of top-level declarations of package server,
each printed with its doc comment, is equal before and after; every line of
the old file other than the two banners appears in the new files, and the new
files add only headers and imports. gofmt, build, vet, the console lint and
the lint snapshot (removed 0, added 0) are clean. The internal/server and
fedwiki packages pass on a reset test database.
The break list is the last new-side record of each break name from the
earlier sections whose text still exists, re-pointed to the destination file:
160 records, all caught on this commit. Five names are dropped because later
sections replaced their text: the rung carries its provision, the flagged
cancellation's date, a failed attachment read refuses the page, a failed
attachment read refuses Checkout, an organization without a pool holds no paid
subscription. Two stay out because they were missed in their own section: an
unmapped price offers no enabled Checkout, and an organization without a
subscription gets no switch confirmation.
Adds internal/testkit/world (New, Providers, Person, Org, Workspace,
Product, Rule, Confer, Person.SignIn), internal/testkit/authtest
(NewSessionManager, SignIn) and internal/integrations/fedwiki/fedwikitest
(the environment, tenant, limit and usage, operator root, site rows), and
moves the FedWiki fixtures onto them: newReqEnv, setupQuotaFixture,
newReconcileTestEnv, siblingWorkspace, governFedwikiLimit (Product, Rule,
Confer, no materialization), newSiteEnv, newClaimTestDB (its raw provider
insert goes), seedOperatorSites, newMemberWorkspace, seedUnderstatedQuota,
newTestWorkspace, seedCountedWorkspace and addSiblingWorkspace.
newReconcileDomainsDB and setupReconcileRoot had nothing left to move. The
six sessions the FedWiki web tests build go through authtest. The import
guard covers the three packages, and scripts/deadcode-testonly.txt lists
them.
The six hand-written lock-wait loops become dbtest.AwaitLockWait
(lifecycle_db_test.go, registration_db_test.go, invoice_reconcile_test.go,
stamp_db_test.go, and the two record-swap tests in requests_db_test.go and
swap_record_db_test.go). Backend gains BlockedBy, Query and Abort. The
registration loop is not a verbatim move: its select matches by query text
within the current database and its done check becomes the Abort probe, as
do the two record-swap loops, each keeping its message ("registration
finished (%v) while the first key was held", "the record finished without
writing the activated site's status: %v", "the record finished without
waiting on the parked request: %v"). Kept weaker on purpose: the
never-waited messages are now the helper's, which names the selection
("backend N never waited on a lock" for the pid loops, "no backend waited on
a lock held by backend N" for the record swaps, "no backend running a
statement containing %q waited on a lock" for the registration and invoice
loops), the record-swap and stamp bounds are the helper's ten seconds (were
the tests' thirty), the stamp loop accepts any waiting count (was exactly
one for its pid), and the invoice loop matches the statement text anywhere
in the query (was its prefix). TestDispatchStampsLockRecordsInOneOrder drops
from cognitive complexity 39 to 31; its pin is re-pinned to 31.
Names and results of the three FedWiki packages, internal/integration,
internal/workflows/desiredstate and internal/integrations/stripe/workflows
equal the parent's apart from the new tests (the import guard's forbidden
list with its three subtests, and the lock helper's selection test with its
three subtests and its Abort test) and one change: the three
TestReconcileDomains* tests in internal/integrations/fedwiki now pass on a
freshly reset database. They failed there at the parent, alone and in the
package run, because nothing had registered FedWiki; the fixture now does
(#33).
Fixture equivalence (each fixture run alone on a reset database, every table
of core, fedwiki and domains dumped before and after). Differences, none
read by an assertion: names and addresses carry the builder's shape
(organization "<name> <suffix>", email "<name>-<suffix>@example.test",
subject "sub-<name>-<suffix>"; the display name is the label for tenant
fixtures); the pool is the organization's default pool (name "Default", key
default, auto-managed) where the fixtures wrote a bare one; a workspace now
sits in the organization's default pool through a pool assignment where
newMemberWorkspace, seedOperatorSites and the claim fixtures wrote neither
(seedOperatorSites gains two pools and two assignments); every FedWiki
fixture registers FedWiki and the System tenant, which adds the provider row
(or fills operator_surface_path on the row a claim fixture inserted), the
operations, connection, state and outbox rows, the System tenant's user,
person, 'system' organization type, organization and workspace, and
refreshes the fedwiki_sites resource key; archived sites carry archived_at,
as the status query sets it; sites are minted under a database-minted id;
governing a limit goes through the rule-change function, which adds a
set-change row. Claim tokens are random in both.
25 breaks: 19 failed the old test at the parent and the new test here (one
code break per FedWiki test file in the function it pins, and one per
converted lock wait with the lock removed; the three loops with an Abort
probe fail through it with their own message), and six on the kit alone
failed the new test. No assertion in a test body changed.
scripts/sqlc-unused counts a call from a package covered by a ".*" entry of
scripts/deadcode-testonly.txt as a test use, so one list defines the
test-support packages for both checks, and the list's header says so. A query
that only the kit's builders call now reads TEST-ONLY, not used. The check
reads the module in a directory it is given, which lets a test run it over a
fixture module.
New tests: TestScanCountsATestSupportPackageAsTestUse, over a fixture module
whose kit package and app package each call one query, and
TestReadSupportTakesTheWholePackageEntries. The other tests, TestCheck and
TestReadAllow, are unchanged. The output of go run ./scripts/sqlc-unused
equals the baseline taken before step 2 once each line's count of test
references is stripped. 2 breaks, each failed the new tests; they have no old
side.
internal/testkit/htmltest parses rendered HTML and selects from it with CSS
selectors (Parse and ParseIn, One, All, Count, None, Text, Texts, Attr,
HasClass, HTML, Before, InOrder), on golang.org/x/net/html and
github.com/andybalholm/cascadia, which joins go.mod as a direct requirement
(BSD-2-Clause, pure Go, depending only on x/net). A page, which opens with a
doctype or an html element, parses as a whole document, so the attributes of
its html and body elements are kept; any other body parses as a fragment, a
table row under a tbody, an option under a select and anything else under a
body. Both parse with scripting off, so what a noscript holds is elements
that selectors reach, as a browser without JavaScript renders it. Its own
tests: the fragment context for tr and option, the attributes of html and
body, the content of noscript, One's failure message naming the selector,
None, Text, selection by attribute and role, Before, and InOrder. The import
guard gets a row for it, and scripts/deadcode-testonly.txt an entry.
harness_test.go gains the shell assertions both surfaces share, exported for
server_test: the top bar, the account menu, the Identity and Access handoff,
the drawer and the rail's account block. member_nav_test.go and
operator_sidebar_render_test.go select regions by landmark and ARIA
(nav.navbar[aria-label=Account], aside#app-rail, the account group's
aria-labelledby list) and controls by attribute
([data-form="shell.confirm"], the button posting to /logout) instead of
regionBetween, topBarRegion, railRegion and sidebarRegion, read aria-current
on each member rail entry and on both surface switches, check order through
InOrder, and keep a class assertion only where the class is the fact.
sidebarSections selects the entry list instead of slicing the rail string,
and sidebarLinkActive reads the link node instead of matching a regular
expression. The page subtests of TestShellAccountMenu and
TestShellMemberRail call named functions (checkAccountMenuPage,
checkMemberRail); every test and subtest name is unchanged.
Each ban and each count keeps the region the old one covered, or a wider
one; a check that an element is present looks where the element belongs,
such as the trigger and the Get help order in the bar and the entries in the
rail, which is narrower than the body the old strings searched and stricter.
A bar, a rail or a page the old tests searched as a string is searched as
markup (Node.HTML) where the old string covered the element's own tag, the
html and body start tags, or an attribute: the rail's d-lg-none count,
app-rail-signout, d-none d-lg-flex, the identity tooltip, the operator
rail's nav-link count and its ps-4, group and /logout bans, text-danger,
navbar-collapse, the handoff explanation, mt-2 pt-2 border-top, and the
bar's bans over everything before the rail. Counts that were page-wide stay
page-wide: the account menu (any tag), the Get help item, the Operator panel
switch, the dismiss control, the hx-post="/logout" controls (counted as
markup, so data-hx-post counts), the bans on href="/logout" (markup, so any
attribute ending in href counts) and the account-console links; those inside
the rail are counted over the whole rail before they are placed in the
account group: the group's name and email spans, its list, the Identity and
Access, Sign out and Get help entries. The Dashboard link count and the
Operator link ban also run over the page's markup, the drawer's two lists
are counted by class prefix as well, and nothing before the account group
reads Sign out. Where the old tests pinned an element's class as an exact
string, the new ones compare its class token set exactly (AssertClassSet),
which ignores token order only; the checks that were prefixes or regular
expressions stay HasClass or class selectors, and an active operator entry
must still carry nav-link. aria-current appears on the active rail entry and
nowhere else in the member rail, and on neither surface switch nor the
account group's entries. Sign out, Get help and the account group's name and
email stay plain text, the trigger's one child is its label span, the menu
header's children are the line break and the email span, and the line break
follows the name. The closing </main> check stays a text check, since a
parser closes the element anyway.
One weakening is general: a markup change that alters no asserted fact no
longer fails, such as an attribute added to an element the old tests matched
as a whole markup string (a data attribute on Sign out, say), a wrapper, or
a decorative child the comments do not exclude. No specific fact is kept
weaker.
The four gocognit exclusions of TestShellAccountMenu, TestShellMemberRail,
TestShellRailAccountBlock and TestOperatorShellTopBarAndDrawer are gone; the
lint reports nothing in their place.
124 breaks: 118 failed the old and the new shell tests (the template and
code changes that move, drop or mislabel each asserted element; an untitled
Get help item in the bar, the drawer's header and the rail; a Log out title
on the brand link; an extra class on Sign out, on the email span, on the
switch's li and on the page title; a missing class on the rail or on an
account entry's li; a button among the rail's entries; a surface label, a
Get help link or a danger Dashboard link between the bar and the rail; a
logout button in the drawer's header or a logout form among its entries; the
tooltip on the li or on the account trigger; an Operator panel label for a
member without the role; a noscript holding a second main, a Dashboard link
or a Sign out link to /logout; a data-hx-post Sign out in the content or the
menu, and a data-href to /logout; aria-current on a surface switch or on
Identity and Access; an active Billing entry without nav-link; a second
account-console link or Identity and Access item; an element inside Sign
out, the trigger, the menu header or the group's name; a class on the rail's
own tag, on the html or body tag, or a second element where the old string
counted one: the account menu, Get help, the Operator panel switch, a plain
Sign out, the group's name and email, its list; a Sign out button before the
account group on either surface; a Dashboard or an Operator link with a
decorative child in the content; a third nav list; a bar without its navbar
class), and 6 on the helper's own tests have no old side. The names and
results of internal/server equal the parent's; new tests:
TestParseKeepsRowsAndOptionsThatABodyContextDrops,
TestParseKeepsTheAttributesOfHtmlAndBody, TestParseSelectsInsideNoscript,
TestOneFailureNamesTheSelector,
TestNoneFailureNamesTheSelectorAndQuotesTheMatch,
TestTextCollapsesWhitespaceAndSelectsInDocumentOrder,
TestSelectionByAttributeAndRole, TestBeforeHoldsWhatPrecedesTheMatch and
TestInOrder in htmltest, and the guard's htmltest subtest.
MemberSet joins OperatorSet in harness_test.go. The member, root and
parts-only sets in the server tests go: rootTemplates, errorPageTemplates,
renderIndex, renderEntitlements, memberTestTemplates, renderMemberDomains,
the inline set in the member invoices test, the parts-only sets of the
form-part and help-icon tests, and renderMember, which now calls Render on
MemberSet. partsSet and renderPart go with them; the part tests render on
OperatorSet, because they assert the operator crumb, and read the error of
TestPageHeaderRejectsCountAndAction from ExecuteTemplate directly. The set
of error_page_test.go that has no error.html stays synthetic.
With the last test callers gone, web.ParseUIPartials, server.helpIcon,
server.OperatorSurfaceRoot, server.MemberSurfaceRoot, server.rootLink and
the server.HelpIcon type are deleted, with their scripts/deadcode-testonly.txt
entries and the comments that described them (the partials' header comments
now name the surface constructors).
The names and results of internal/server and internal/web equal the
parent's. 12 breaks (one per helper replaced, a text change in the template
its first caller renders), each failed the old and the new test.
web.MemberTemplates builds the member set from CoreTemplates and each
integration's Mount, and composeUITemplates folds into it; operator.html and
the operator-side integration files leave the root set. Start and the six
member handlers call it. TestComposeUITemplates moves to internal/web as
TestMemberTemplatesMounts, its subtests keeping their names except the last,
now "template name without a key prefix is an error" (the constructor returns
the error and Start panics through template.Must). New tests:
TestMemberTemplatesHoldTheMemberFiles. 1 command break, caught by make
screens on the new side; the constructor is new, so no old run.
web.OperatorTemplates builds the operator set from CoreTemplates and each
integration's Mount, registering the whole function map before it parses;
the four operator handlers call it. New tests, in internal/web:
TestOperatorTemplatesHoldTheOperatorFiles,
TestOperatorTemplatesParseASharedPartOnce,
TestOperatorTemplatesRefuseAMountFileWithoutItsKeyPrefix and
TestParseSourcesRefuseTwoFilesWithOneBaseName. 1 command break, caught by
make screens on the new side; the constructor is new, so no old run.
internal/testkit/dbtest opens TEST_DATABASE_URL and E2E_DATABASE_URL, never
migrates, and fails with "the test database is behind; run
test/reset-test-db.sh" when the core ledger lacks the highest version of
db.BaseSources(). The opener helpers except the scratch-database and
container openers (26 of the 29) and the inline openers of the database
tests call it; helpers that did more than open (providers, fake farms, the
FedWiki provider row, cleanup of override rows) keep that setup. The
stripe/workflows and workflows/entitlements openers keep their search_path
through WithSearchPath. The closed-database seam tests use Closed.
TestMigration18SetsExistingFedwikiRulesToForceReduce runs on a scratch
database through Scratch and UpTo instead of rolling the shared core ledger
back through goose's global state, because no opener migrates the shared
database any more.
The in-package forms tests of internal/server reached the integrations'
forms only because an external test file imported internal/migrate; a test
file now links internal/integrations explicitly.
TestRegisterProviders_DB is re-pinned from gocognit 37 to 34.
New tests: TestLedgerCheckRefusesAnEmptyDatabaseAndAcceptsTheTestDatabase
(dbtest), TestOpenRefusesAnEmptyDatabaseAndAcceptsTheTestDatabase (dbtest,
through opened, the path Open and OpenE2E share),
TestKitPackagesReachOnlyTheirAllowedModulePackages (testkit). The skip
messages of the openers are unified to "TEST_DATABASE_URL not set, skipping
integration test". 4 breaks: Open skipping its ledger check and the ledger
check accepting an empty database fail the new tests; a kit row that no
longer allows internal/testkit fails the import guard; leaving the fedwiki
rules as they were in migration 00018 fails the migration 18 test on both
sides.
scripts/mutation-check reads a JSON list of breaks, applies each to the tree
one at a time, restores the file byte for byte, and reports CAUGHT, MISSED,
WRONG-MESSAGE, NOT-FOUND, SKIPPED or CONTROL-FAILED for each.
New tests: TestApplyAndRestoreAreByteExact, TestOldTextMustOccurOnce,
TestSidePicksPatternAndMessage, TestARecordWithASideRunsOnlyThere,
TestOutcomes, TestStatelessListStopsOnAFailingControl,
TestControlRunsOncePerDistinctTest,
TestStatefulListPreparesAndControlsEachBreak,
TestStatefulControlFailureMarksOnlyThatBreak, TestPrepareFailureStopsTheList,
TestCommandRecordsAreClassifiedByExitAndText,
TestCommandRecordReadsTheInspectedFileAndNeverAStaleOne,
TestCommandControlFailsWhenTheTextIsAlreadyThere,
TestSummarizeExitsOneUnlessEveryBreakIsCaught.
Stripe delivers webhook events in the endpoint's API version. Under a
version later than the 2025-02-24.acacia stripe-go v81 pins, the
payload-shaped invoice handlers projected every invoice with no
subscription link and no lines, and every payment with no payment
intent or method.
- Invoice events read only the invoice id and run ReconcileInvoice,
which reads the invoice, its lines and its payment intent's charges
back at the pinned version and converges the core rows under a
per-invoice lock, in any event order. A payment row is one attempt,
keyed by its Stripe charge (Stripe store migration 00005).
- Two clocks: created_at is when the console wrote a row; invoices gain
issued_at and payments paid_at (core migration 00022, backfilled),
and every invoice and payment list, the activity feed and the
composite's latest invoice read those.
- An hourly and at-boot invoice sweep reads back invoices never read,
open invoices once a day, and invoices of dead-lettered events; the
provider page states when it last finished. The first run is the
repair of invoices projected so far.
- The API version skew is logged once per version, and
payment_method.attached links payments recorded before it.
- A restricted key now needs Invoices read and Charges read.
- scripts/db-reader-role.sql maintains console_reader, a login role
that reads the console's tables without personal data, documented in
docs/database-management.md and checked against a golden column list
by TestConsoleReaderColumns. The demo snapshot keeps owners and
privileges so the role's checks hold on it.
- The demo seed and test fixtures carry issue and paid times.
status/design-feedback.md gains four upstream notes, one written when
#60 was closed.
OpenSpec change stripe-invoice-reconcile is archived and its deltas
synced into openspec/specs/. Follow-ups are #150, #151 and #152.
The contained runner that drove the 2026-09 security audit, the README
review and four rounds of design ideation lived only inside the ignored
notebook. It moves to scripts/agent-runner/: the prepare, tools, audit,
ideation and teardown scripts, the compose and container files, the
allowlist proxy and the prompt templates, with the paths that assumed
the notebook fixed and findings written to a caller-named directory. Run
outputs, transcripts and the round-specific sheet scripts stay behind.
docs/agent-runner.md states the method: the disposable git archive
copy and the fail-closed proxy, how a task is shaped, union rather than
intersection of findings across models, separate adjudication of every
finding against the source, and the evidence a finding must carry.
A notebook's lessons survive only if they are written into a tracked
home before the decision lands, so landing a change now has three
pieces. The contract: a "Landing a change" section in MAINTAINING.md and
a fifth item in the UI definition of done. The labor: a harvest skill
that inventories the notebook, names each file's kind and routes
findings to the owning docs page, reasoning to the change's design.md,
problems to issues.md and reusable tools to a tracked home. The gate:
scripts/notebook-citations.sh, a git grep over what git would commit for
the notebook's directory shape, which make lint runs after the program's
own lint, and scripts/archive-gate.sh, which refuses an mv into the
archive or an openspec archive while the check reports hits.
The gate is one script that any harness calls with the command it is
about to run; Claude Code reaches it through a PreToolUse hook and
opencode through a plugin. Both checks live in scripts/ rather than the
lint subcommand because they are repository hygiene, not part of the
shipped program. The generated openspec-archive-change skill is not
edited; the post-archive reminder carries the harvest step instead.
Introduce a commercial license option alongside AGPL-3.0-only, require a
CLA for contributors, and document the terms in COMMERCIAL.md and
NOTICE. Add a script to stamp SPDX headers on Go files and apply it
across the tree.