Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
395afb9
test(config): expose implicit runtime provisioning authority
seonghobae Aug 14, 2026
bcae84c
ci: repair runtime store provisioning boundary
seonghobae Aug 14, 2026
f9734f3
build: stage runtime provisioning repair helper
seonghobae Aug 14, 2026
b509cc4
ci: simplify runtime provisioning repair runner
seonghobae Aug 14, 2026
0b7fc49
ci: remove self-modifying runtime repair writer
seonghobae Aug 14, 2026
2a56dc2
chore: remove one-shot runtime repair helper
seonghobae Aug 14, 2026
7c152cd
ci: add read-only runtime provisioning probe
seonghobae Aug 14, 2026
12262f5
ci: add deterministic runtime provisioning candidate generator
seonghobae Aug 14, 2026
3655519
ci: repair read-only runtime provisioning probe
seonghobae Aug 14, 2026
71a5547
ci: preserve read-only setup seams in runtime candidate
seonghobae Aug 14, 2026
cc1dd88
ci: bound schema diagnostics without exception context
seonghobae Aug 14, 2026
7882883
fix(config): remove runtime schema provisioning authority
seonghobae Aug 14, 2026
01a7ae2
fix(config): seed runtime defaults at schema boundary
seonghobae Aug 14, 2026
237e578
ci: finalize verified runtime provisioning repair
seonghobae Aug 14, 2026
e639f48
fix(config): finalize least-privilege runtime stores
seonghobae Aug 14, 2026
0c88d83
test(config): reject selectable incompatible runtime relations
seonghobae Aug 14, 2026
472e738
fix(config): validate provisioned relation kind and column types
seonghobae Aug 14, 2026
e2b5fad
test(config): model provisioned runtime catalog metadata
seonghobae Aug 14, 2026
5b86445
docs(config): define catalog compatibility and runtime role boundary
seonghobae Aug 14, 2026
384cc25
test(config): prove runtime read privilege boundary
seonghobae Aug 14, 2026
ab81331
fix(config): fail closed on runtime read privilege gaps
seonghobae Aug 14, 2026
33f231e
test(config): model runtime catalog privilege evidence
seonghobae Aug 14, 2026
45f102e
docs(config): bound search-path trust claim
seonghobae Aug 14, 2026
afaead9
test(config): assert structured ConfigError contract
seonghobae Aug 14, 2026
37174e6
test(security): require encrypted secret storage by default
seonghobae Aug 14, 2026
7a29783
fix(security): require secret encryption by default
seonghobae Aug 14, 2026
f5369b2
test(security): make insecure secret storage opt-in
seonghobae Aug 14, 2026
6e7564a
test(security): preserve local lifecycle coverage explicitly
seonghobae Aug 14, 2026
16574b6
test(security): make integration obfuscation explicit
seonghobae Aug 14, 2026
995c33f
docs(security): document encrypted secret-store default
seonghobae Aug 14, 2026
f2d3088
test(config): isolate provisioning boundary from encryption policy
seonghobae Aug 14, 2026
4d0f317
test(config): prove malformed Fernet keys fail before database access
seonghobae Aug 14, 2026
9c4028e
fix(config): validate Fernet key before database access
seonghobae Aug 14, 2026
40a9abb
test(integration): avoid persisting unused plaintext secret
seonghobae Aug 14, 2026
437a109
test(config): require writable-key schema compatibility
seonghobae Aug 14, 2026
9226e87
fix(config): verify unique runtime storage keys
seonghobae Aug 14, 2026
c618bed
test(config): model unique runtime storage keys
seonghobae Aug 14, 2026
a5fc816
test(config): complete unique-key compatibility harness
seonghobae Aug 14, 2026
42be189
docs(doctoring): record unique-key runtime invariant
seonghobae Aug 14, 2026
7c390a8
test(config): reject include-only storage key indexes
seonghobae Aug 15, 2026
ca6a289
fix(config): ignore INCLUDE payload columns for uniqueness
seonghobae Aug 15, 2026
1909321
docs(config): clarify unique key versus INCLUDE payload
seonghobae Aug 15, 2026
3ea0ffb
test(config): reject deferrable conflict arbiters
seonghobae Aug 15, 2026
4d41735
fix(config): exclude deferrable conflict arbiters
seonghobae Aug 15, 2026
33ca438
docs(config): record conflict-arbiter constraint boundary
seonghobae Aug 15, 2026
38eaf9c
test(config): fail closed on corrupt stored secrets
seonghobae Aug 15, 2026
1471ce9
fix(config): reject corrupt stored secret encodings
seonghobae Aug 15, 2026
7d6c6f8
test(config): reject coerced persisted encryption flags
seonghobae Aug 15, 2026
c6ab8bc
fix(config): preserve persisted encryption flag type
seonghobae Aug 15, 2026
762a72e
test(config): require encrypted legacy-secret readiness
seonghobae Aug 15, 2026
2669838
fix(config): reject unencrypted legacy rows in required mode
seonghobae Aug 15, 2026
1c127d9
test(config): model encrypted-row readiness in fake store
seonghobae Aug 15, 2026
cfd8578
test(config): reject late unencrypted rows in required mode
seonghobae Aug 15, 2026
f7e7904
fix(config): enforce required encryption on reads
seonghobae Aug 15, 2026
da19e1f
test(config): initialize decode-boundary policy state
seonghobae Aug 15, 2026
003183a
test(secrets): reject unencrypted compatibility mode
seonghobae Aug 15, 2026
2f56333
fix(secrets): make Fernet storage mandatory
seonghobae Aug 15, 2026
085fd6a
test(secrets): require encrypted store fixtures
seonghobae Aug 15, 2026
0dec24b
test(secrets): preserve connection cleanup under mandatory encryption
seonghobae Aug 15, 2026
e8258db
test(secrets): reject legacy plaintext decode paths
seonghobae Aug 15, 2026
a8e6288
fix(secrets): preserve exact encryption-flag validation
seonghobae Aug 15, 2026
c3c6559
docs(secrets): remove insecure compatibility guidance
seonghobae Aug 15, 2026
0697d57
test(secrets): exercise runtime probes with encryption enabled
seonghobae Aug 15, 2026
4f9473c
test(secrets): keep index probes behind valid encryption
seonghobae Aug 15, 2026
a3f16fb
test(config): reject Fernet key subclasses
seonghobae Aug 16, 2026
7d04484
fix(config): require exact Fernet key text
seonghobae Aug 16, 2026
7f421f4
Merge branch 'main' into fix/runtime-store-provisioning-d0a4b30
opencode-agent[bot] Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion docker/postgres/init/02_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ CREATE TABLE IF NOT EXISTS com_secrets (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);


-- Provision built-in configuration only at the explicit schema boundary.
-- Existing operator values are never overwritten during upgrades or replay.
INSERT INTO com_config (config_key, config_value, config_description)
VALUES
('batch_size.min', '100', 'Batch request size limit'),
('batch_size.default', '50000', 'Batch request size limit'),
('batch_size.max', '50000', 'Batch request size limit'),
('token_limits.per_batch', '5000000000', 'Token count limits'),
('token_limits.per_request', '128000', 'Token count limits'),
('token_limits.buffer_percentage', '5', 'Token count limits'),
('azure_limits.max_records_per_file', '100000', 'Batch upload constraints'),
('azure_limits.max_bytes_per_file', '209715200', 'Batch upload constraints'),
('azure_limits.max_files_per_job', '500', 'Batch upload constraints'),
('optimization.auto_split', 'true', 'Optimization features'),
('optimization.smart_batching', 'true', 'Optimization features')
ON CONFLICT (config_key) DO NOTHING;

-- =============================================================================
-- Queues and batches
-- =============================================================================
Expand Down Expand Up @@ -428,4 +446,4 @@ BEGIN
WHERE table_name = 'com_config'),
'kv config store'::TEXT;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql;
59 changes: 59 additions & 0 deletions docs/doctoring/runtime-store-provisioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Runtime store provisioning boundary

## Operator action

Provision or migrate `com_config` and `com_secrets` with the explicit package schema before starting an ordinary application process:

```bash
python -m pg_llm_batch init-db
```

After provisioning, the normal application role needs schema `USAGE` plus only the table privileges required by its configured read/write operations. It does not need schema `CREATE` merely to construct `PostgresConfigStore` or `SecretStore`. Production application roles must be `NOSUPERUSER NOBYPASSRLS` so administrative privilege cannot silently bypass row-security boundaries owned elsewhere in the package schema.

Runtime constructors issue bounded read-only `pg_catalog` capability probes. The search-path-resolved relation must be an ordinary base table, every required column must have the expected PostgreSQL data type, and the current role must have schema `USAGE` plus table `SELECT`. The configured storage key (`config_key` or `secret_key`) must also be backed by a valid, ready, non-partial, non-expression unique index with exactly one key column so the runtime `ON CONFLICT (key)` write contract has a compatible arbiter before application writes begin. If that index backs a PostgreSQL constraint, the constraint must be `NOT DEFERRABLE`; PostgreSQL does not permit a `DEFERRABLE` constraint to act as an `ON CONFLICT` arbiter. A storage key that appears only as an `INCLUDE` payload column does not satisfy this boundary because PostgreSQL explicitly excludes included columns from uniqueness enforcement. Missing tables, missing columns, selectable views, type mismatches, insufficient read privileges, missing or deferrable unique-key authority, or other incompatible relations fail closed with fixed package diagnostics; database exception text, DSNs, SQL values, and credentials are not retained in exported exception context. Connections acquired before a failed probe are closed deterministically.

Built-in configuration rows are seeded only at the explicit schema boundary with `ON CONFLICT (config_key) DO NOTHING`. Existing operator values therefore survive provisioning replay and upgrades. The packaged schema and deployable PostgreSQL initialization schema remain byte-identical.

## Secret encryption policy

`SecretStore` requires Fernet encryption for every runtime construction and every newly persisted secret. A missing Fernet key, malformed key, unavailable cryptography dependency, or explicit `require_encryption=False` request fails with a fixed `ConfigError` before PostgreSQL connection acquisition. The historical `require_encryption` keyword is retained only so existing callers that explicitly pass `True` do not break at the call boundary; it is not a policy switch and cannot re-enable reversible Base64 persistence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph already requires Fernet and rejects require_encryption=False, but the files operators follow on this head still disagree.

On 7f421f4, CHANGELOG.md has no Unreleased entry for this break, README.md still says “optional Fernet key” and still shows SecretStore(dsn), bootstrap.py / docs/doctoring/bootstrap-dsn-precedence.md still call the key optional, and ARCHITECTURE.md does not record the provisioning vs runtime split or the non-equivalent Base64 rollback.

Update those documents on this branch, or take the continuation on cursor/bc-389287fc-f6ac-4e1c-9047-5b223d434a8e-729f, before treating this head as mergeable.


Runtime secret writes produce Fernet ciphertext only. Runtime reads reject any durable row whose encryption flag is not the exact boolean `TRUE`; the runtime no longer Base64-decodes historical unencrypted values. Startup also positively proves that `com_secrets` contains no `is_encrypted IS NOT TRUE` row before accepting the store. A deployment with historical unencrypted rows must therefore inventory and migrate them through a separately reviewed, atomic, recoverable migration before starting the hardened runtime. Do not convert those rows in-place without a verified backup, rollback procedure, key-custody plan, and post-migration proof that no unencrypted rows remain.

This boundary is intentionally narrower than the complete secret lifecycle tracked separately. It does not itself perform the historical-row migration, rotate keys, retire decrypt-only keys, prove key custody, define retention, or convert successful encryption into a SOC 2, CSAP, or NIST certification claim.

## Security rationale

PostgreSQL grants object creation through a schema's `CREATE` privilege; ordinary access requires separate schema `USAGE` and object privileges. Removing implicit runtime DDL permits a least-privilege application role. The runtime probe validates the relation resolved through the active `search_path`; it does not establish that relation's schema ownership. Operators must therefore use a trusted search path and prevent untrusted roles from creating objects in schemas searched ahead of the provisioned package relation. PostgreSQL also states that `CREATE TABLE IF NOT EXISTS` does not prove that an existing relation has the expected structure, so an implicit create statement is not a schema-compatibility check. Runtime compatibility therefore derives from bounded read-only catalog and privilege metadata for the resolved relation rather than from successful execution against an arbitrary selectable relation.

PostgreSQL implements uniqueness through unique indexes and resolves `INSERT ... ON CONFLICT` using unique-index inference. `pg_index.indisunique`, `indisvalid`, and `indisready` distinguish indexes that are unique, valid, and ready for inserts; `indnkeyatts` distinguishes key columns from included attributes, while `indpred` and `indexprs` identify partial and expression indexes. PostgreSQL's `CREATE INDEX` contract further specifies that `INCLUDE` columns are non-key payload and are disregarded for uniqueness or exclusion constraints. PostgreSQL also limits `ON CONFLICT` arbiters to non-deferrable uniqueness authority: when a qualifying index backs a constraint, the probe correlates `pg_constraint.conindid` with the index and rejects `condeferrable = true`. The runtime compatibility probe therefore inspects only the first `indnkeyatts` entries of `indkey` when matching the storage key and rejects both a readable lookalike table whose storage key is merely included behind some other unique key and one whose only matching uniqueness is deferrable. Neither shape can satisfy the package's simple-column `ON CONFLICT (config_key)` or `ON CONFLICT (secret_key)` write contract.

Fernet provides authenticated symmetric encryption for stored secret values when a valid key is configured, but the cryptographic primitive does not by itself define deployment authorization, key custody, rotation, recovery, retention, or audit policy. Mandatory runtime encryption removes the previous insecure compatibility path instead of treating reversible obfuscation as an acceptable environment-specific fallback.

## Rollback and recovery

Rollback of unrelated runtime-store provisioning changes must preserve the deployment's Fernet key and mandatory encryption posture. Older package versions that permit Base64 fallback reopen the at-rest confidentiality defect and are not a security-equivalent rollback target. Before any rollback across this security boundary, verify the target version's secret-store behavior, retain encrypted database backups and key-recovery material under the deployment's custody controls, and prove that rollback will not create or decode unencrypted secret rows.

A failed runtime schema probe is repaired by applying the reviewed schema with a provisioning identity and retrying with the ordinary runtime identity. A failed encryption-readiness probe is repaired by the separately reviewed historical-row migration, not by disabling encryption. Do not grant broad schema `CREATE`, `SUPERUSER`, or `BYPASSRLS`, add an untrusted writable schema ahead of the provisioned relation in `search_path`, or weaken secret encryption merely to silence readiness.

## References

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: CREATE INDEX*. https://www.postgresql.org/docs/18/sql-createindex.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: CREATE TABLE*. https://www.postgresql.org/docs/18/sql-createtable.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: INSERT*. https://www.postgresql.org/docs/18/sql-insert.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: pg_constraint*. https://www.postgresql.org/docs/18/catalog-pg-constraint.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: pg_index*. https://www.postgresql.org/docs/18/catalog-pg-index.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Privileges*. https://www.postgresql.org/docs/18/ddl-priv.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Schemas*. https://www.postgresql.org/docs/18/ddl-schemas.html

The Psycopg Team. (2026). *Psycopg 3 documentation: Basic module usage*. https://www.psycopg.org/psycopg3/docs/basic/usage.html

The cryptography developers. (2026). *Fernet (symmetric encryption)*. https://cryptography.io/en/latest/fernet/

Joint Task Force. (2025). *Security and privacy controls for information systems and organizations* (NIST Special Publication 800-53, Revision 5, Release 5.2.0). National Institute of Standards and Technology. https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final
Loading
Loading