Skip to content

feat(cli): collapse the surface to site and bench - #7

Merged
mrcsin merged 2 commits into
masterfrom
setup-commands
Aug 24, 2026
Merged

mrcsin merged 2 commits into
masterfrom
setup-commands

Conversation

@mrcsin

@mrcsin mrcsin commented Aug 24, 2026

Copy link
Copy Markdown
Member

semibase is a setup script: it brings an instance to a known state and exits, run once at commissioning and never again. The surface was named for its internal steps — config, create, verify, all — and all was named for its mechanism, with exactly one caller on Earth: this repository's own CI.

Two commands

semibase site    # memory tuning + database, roles, grants, semiplot_tags, trends
semibase bench   # the same without the tuning

They differ in one thing. That is only true because public.trends moved into this tool and is now created in both modes — previously Simple-Scada made it on a site and SemiPlot's seeder made it on a bench, which was the second difference and the reason a bench tested a shape a site never had.

verify is gone as a command. It existed as a separate verb because its checks had to wait for the SCADA to create the table; once the tool creates it, they are knowable at exit and became the tail of both commands. Nobody was ever going to return and run it by hand.

No aliases, no deprecation: the tool is in development and its only consumer is ours.

Two review findings worth reading, both measured

The privilege check did not check what it claimed. has_table_privilege reads a catalog bit. After REVOKE USAGE ON SCHEMA public FROM public the tool reported "semiplot_reader holds SELECT" and exited 0, while an actual SELECT failed with permission denied for schema public. The load-bearing check is now a real read; the catalog question survives only to split "no table grant" from "cannot enter the schema", each with its own repair.

Creating the table over a scada_writer login added an undocumented pg_hba requirement. On a socket with local all all peer — the default on Debian, Ubuntu and RHEL — the login failed with peer authentication after the database, both roles, the grants and semiplot_tags were already created. Reproduced against the pre-fix binary, then fixed by SET ROLE on the superuser connection. Measured A/B: identical owner and identical ACL, semiplot_reader=r/scada_writer. The fidelity argument for a real login was about archiving, not about creating a table.

A run carrying only SEMIBASE_SUPER_PASSWORD now completes end to end over a socket.

Verified against containers, not argued

bench and site on a fresh postgres:17-alpine, twice each for idempotency; the revoked-schema case now fails with the repair named; a trends missing tpdefault is caught rather than reported as "left untouched" (it accepts no inserts); a non-empty tpdefault warns that a day partition was missing at write time; host all semiplot_reader all reject proves the live reader login is load-bearing when it runs; and the init-script path completes during initdb over the socket.

One thing recorded rather than solved

Whether Simple-Scada tolerates finding trends already present is unmeasured. The expectation is that it writes into the existing table the way it would after a reconnect, and the shape it finds is the shape it makes — but nobody has watched it. docs/architecture/provisioning.md carries it as an explicitly unverified assumption with the experiment that settles it: log_statement = 'all', one SCADA start against a semibase site database, read the DDL from the log.

The locked-decisions table is amended in the same PR: the vendor still owns the schema's evolution; this tool creates the table once with the vendor's shape and never alters it.

🤖 Generated with Claude Code

mrcsin and others added 2 commits August 24, 2026 13:08
semibase is a setup script: one run brings an instance to a known state
and exits. The old surface named the tool's internal steps (config,
create, verify) plus a mechanism (all, whose only caller was this
repository's CI). It is now two commands named for the two situations,
and they differ in exactly one thing: site applies the ALTER SYSTEM
memory constants, bench does not. config, create, verify and all are
removed with no alias and no deprecation path.

public.trends moves into this tool, in both modes. It was created by
Simple-Scada 2 on a site and by SemiPlot's seeder on a bench, so the
reader's access to it was unknowable until after the SCADA's first
start. sql/trends.sql carries the vendor's shape verbatim - partitioned
by range on t, primary key tpk, the tpdefault catch-all - embedded like
sql/semiplot_tags.sql and applied over a scada_writer login of its own,
never SET ROLE, so the reader's SELECT arrives through the default
privileges set for that role. messages is not created and day
partitions are not created; both stay with their writer.

verify's two load-bearing checks become the tail of both commands,
where they are now knowable: the reader holds SELECT on public.trends
and does not hold INSERT, plus the pending-restart warning on site.
The messages check, the "writer has not run yet" inference and the
asPartOfAll branch go with the commands they served.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tail check asked has_table_privilege, which reads one catalog bit. After
REVOKE USAGE ON SCHEMA public FROM public it answers t while every read the
reader issues fails with "permission denied for schema public" - measured on
postgres:17-alpine. The read itself is the check now: SET ROLE semiplot_reader,
then SELECT count(*) FROM (SELECT 1 FROM public.trends LIMIT 1) probe. The
catalog bit is asked only after that read has failed, to split a missing table
grant from a schema the reader cannot enter; each answer prints its own repair,
and the grant repair again names both statements and the re-run. Over TCP, with
a reader password in the run, the reader also logs in and reads - the half
SET ROLE cannot reach, being pg_hba admission and the password a consumer
carries. It is skipped with a note on a socket host and without that password.

public.trends is created under SET ROLE scada_writer instead of over a
scada_writer login. The login made the run depend on pg_hba admitting that
role: on a socket with "local all all peer", the default on Debian, Ubuntu and
RHEL, the superuser phases succeeded and the run then died with SQLSTATE 28000,
leaving the database, both roles, the grants and semiplot_tags behind. Both
routes were measured to leave the same relowner and the same relacl, so nothing
about the reader's access path changes. Creating the table therefore needs no
writer password; that password is now needed only on a first run, to create the
role, and the usage text says so.

The re-run path no longer says "exists, left untouched" and stops there: it
requires public.tpdefault, since without it a row no day partition covers is
rejected, and reports the tpdefault row count, which on a months-old table is
the only signal that a day partition was missing at write time.

The pending-restart read moves to the last statement of the tail, where the
docs already claimed it was, and both commands run it - bench tunes nothing,
but it runs against servers a site run tuned and nobody restarted.

The DDL test pins the whole statement text of sql/trends.sql, columns, types
and defaults included, and its messages guard now matches the unqualified name.
The provisioning-order step and the Russian commissioning runbook hedge the
SCADA-meets-an-existing-trends assumption the way provisioning.md does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mrcsin
mrcsin merged commit dad5f3d into master Aug 24, 2026
2 checks passed
@mrcsin
mrcsin deleted the setup-commands branch August 24, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant