Skip to content

feat(provision): provision the SemiPlot pen catalogue (#9) - #11

Merged
mrcsin merged 1 commit into
masterfrom
semiplot-role-and-pen-schema
Sep 24, 2026
Merged

mrcsin merged 1 commit into
masterfrom
semiplot-role-and-pen-schema

Conversation

@mrcsin

@mrcsin mrcsin commented Sep 24, 2026

Copy link
Copy Markdown
Member

Problem

SemiPlot is about to let an operator edit a pen, and the database could not hold one. The SemiPlot role
could only read, and semiplot_tags carried one group name per pen, no stored scale and no stored
visibility. Nothing created a pen but a person at a console.

What changed

  • semiplot_reader becomes semiplot (--plot-password / SEMIBASE_PLOT_PASSWORD). It reads the
    archive and writes its own configuration only: column-level UPDATE on the eight settings columns of
    semiplot_tags, full DML on semiplot_groups and semiplot_pen_groups, SELECT on semiplot_meta.
    It never inserts, deletes or re-keys a pen: a pen's id is the SCADA variable number.
  • semiplot_tags gains unit, format, enabled_on_start and a paired scale_min/scale_max; groups
    move into their own tables, so a pen can sit in several; semiplot_meta records the schema version.
  • semiplot_register_new_pens() adds a hidden default pen for every key in public.trends that has none.
    It runs as semiplot_registrar (NOLOGIN, minimal grants), pins search_path to pg_catalog, pg_temp
    and names its relations by schema, so semiplot cannot register a key of its own choosing and
    scada_writer cannot reach superuser through it. The remaining reach of scada_writer, which owns
    trends, is stated as the trust model in docs/architecture/provisioning.md#trust.
  • public.trends is now created before the SemiPlot schema files, because a LANGUAGE sql body is
    checked against the tables it names at CREATE FUNCTION.

#8 asked for a second role, semiplot_writer. One role with per-table and per-column grants was
built instead: the two passwords would sit in the same connection.yaml, so a second role separated
nothing, and the boundary that matters is enforced by the grants.

How it was verified

  • go test ./..., go vet, gofmt -l, golangci-lint run: clean.
  • semibase bench twice on fresh postgres:17-alpine and once on postgres:14-alpine: every tail check
    OK, including the refused insert, delete and key change and the temporary-trends shadow replay.
  • Both attacks found in review were reproduced against the earlier code and shown to fail now: a temp
    trends shadow no longer registers a key, and a view swapped in by scada_writer runs as
    semiplot_registrar (super=f) instead of postgres (super=t).
  • The new CI step writes real trends rows and checks the function returns 3, then 0, with the expected
    defaults.
  • SemiPlot's full suite (1027 unit, 97 integration) ran green against an image built from this branch,
    and the operator walked the SemiPlot demo stand on it.

Closes #9
Closes #8

Per-task commits before collapse
f86ae8e docs(cli): describe the registrar and the pen check
19351a7 docs(plans): record verification for tasks 8 and 9
3be448d docs(provision): cut restated and stale comments
382fadb refactor(provision): fix conventions review findings
5e80b76 fix(provision): state the trust model, drop dead check
bf031e8 fix(provision): run pen registration as its own role
62399f5 feat(provision): register new pens from trends keys
222c014 feat(provision): narrow the semiplot grant on semiplot_tags
478c51c docs(provision): cut comments the docs already carry
fe55123 docs(provision): correct the column-order rationale
0b6a4ef feat(sql): add the pen format column
19925b8 refactor(provision): drop the dead migration
c788577 docs(plans): record the exec run and its checks
1d10459 docs: cite symbols and correct the migration claim
9399ba5 docs(provision): correct and thin the branch comments
83529fc refactor(provision): split the phases and cut smells
5667dd0 fix(provision): refuse a newer schema before any write
e2acf97 fix(provision): stamp the version inside the migration
1d35bc2 fix(provision): gate the upgrade path in CI
c00520b docs: state the semiplot role and pen schema
5659144 docs(plans): record the acceptance pass
fde6891 feat(provision): check the semiplot write chain
6b21b5e feat(provision): grant the SemiPlot role its own tables
40fbf48 feat(sql): migrate an existing SemiPlot schema
0490454 feat(sql): state the SemiPlot pen and group schema
d179d9c refactor(provision): rename the reader role to semiplot
6324f31 docs(plans): add the semiplot role and schema plan

https://claude.ai/code/session_01PfZJa7neMMKPzycFR6iuhD

@mrcsin
mrcsin marked this pull request as ready for review September 24, 2026 13:07
SemiPlot is about to let an operator edit a pen, and the database could
not hold one: the SemiPlot role could only read, and semiplot_tags had
one group name per pen, no stored scale and no stored visibility.

The role semiplot_reader becomes semiplot, and its password moves to
--plot-password / SEMIBASE_PLOT_PASSWORD. It reads the archive as before
and writes its own configuration, and nothing else: a column-level
UPDATE on the eight settings columns of semiplot_tags, full DML on
semiplot_groups and semiplot_pen_groups, SELECT on semiplot_meta. It
never inserts, deletes or re-keys a pen, because a pen's id is the SCADA
variable number and belongs to SCADA.

semiplot_tags gains unit, format, enabled_on_start and a paired
scale_min/scale_max; groups move into semiplot_groups and
semiplot_pen_groups, so a pen can sit in several. semiplot_meta records
the schema version.

New pens come from the keys SCADA writes. semiplot_register_new_pens()
adds a hidden pen with default settings for every key in public.trends
that has none; the viewer calls it at start and from its editor. It
runs as semiplot_registrar, a NOLOGIN role holding only what the body
needs, pins search_path to pg_catalog, pg_temp and names its relations
by schema, so semiplot cannot make it register a key of its own
choosing, and scada_writer, which owns trends, cannot reach superuser
through it. What scada_writer can still do as the roles that read its
table is stated as the trust model in provisioning.md#trust.

The tail check proves the boundary by execution on every run: the
allowed writes succeed, the insert, delete and key change are refused,
a temporary trends shadow registers nothing, and PUBLIC holds no
EXECUTE on the function. CI exercises the function body over real
trends rows on PostgreSQL 17 and 14.

Claude-Session: https://claude.ai/code/session_01PfZJa7neMMKPzycFR6iuhD
@mrcsin
mrcsin force-pushed the semiplot-role-and-pen-schema branch from a80f182 to 15d5871 Compare September 24, 2026 13:13
@mrcsin
mrcsin merged commit 5498b49 into master Sep 24, 2026
2 checks passed
@mrcsin
mrcsin deleted the semiplot-role-and-pen-schema branch September 24, 2026 13:15
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.

Extend semiplot_tags for pen settings and add group tables Add a semiplot_writer role for SemiPlot's own tables

1 participant