For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Rename three verbosely-named OSCAL SSP tables to short ssp(s)_* names across the jedi-oscal package, the BE main project, and 4 DB environments — with zero data loss and zero runtime regression.
Architecture: In-place ALTER TABLE ... RENAME (data + FK integrity preserved automatically by PostgreSQL — no drop/recreate). ORM (__tablename__ + schema-qualified FK strings + explicit index names) and BE raw SQL must change in lockstep with the DB rename, because old-code-vs-renamed-table and new-code-vs-old-table both crash. jedi-oscal is modified via dev path dependency first; published to Nexus only after the whole change passes smoke.
Tech Stack: Python 3 / SQLAlchemy 2.x (Mapped/mapped_column) / PostgreSQL (schema oscal + compliance) / Poetry / Nexus private PyPI.
| Current | New |
|---|---|
oscal.system_security_plans |
oscal.ssps |
oscal.system_security_plan_control_implementations |
oscal.ssp_control_implementations |
oscal.system_security_plans_system_characteristics |
oscal.ssp_system_characteristics |
Decisions locked with user (2026-06-10):
ssps (plural, matches existing ssp_components / ssp_inventory_items convention).ALTER TABLE).system_security_plan_id (on ssp_control_implementations, ssp_system_characteristics, ssp_control_implementation_objectives) and ssp_id (on components/inventory/leveraged) stay exactly as-is. Do not "tidy" column names — out of scope, would explode the blast radius.ssp_-prefixed tables (ssp_components, ssp_docx_parse_jobs, ssp_reference_documents, ssp_inventory_items, ssp_control_implementation_objectives, ssp_leveraged_authorizations, etc.) are NOT renamed. They are the child tables; they keep their names.compliance-manager-fe) and E2E test (compliance-manager-test) repos: confirmed zero DB-table-name references — no changes there.sspUid, system_security_plan_uid) are not DB table names and are out of scope.DB credential note: All migration psql commands below use
-U cmmgr(required —cm_appis RLS-blocked and silently no-ops system-level writes, per CLAUDE.md). The live.envDB_SECRETcurrently holds thecm_appaccount, but in DEV/STG/POCcmmgrandcm_appshare the SAME password (see memoryreference_dev_db_psql_port), soPGPASSWORD=<查.env DB_SECRET password>works with-U cmmgr. Never write the literal password into any committed file.
jedi-oscal package (~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/):
infra/model/ssp/ssp.py — table system_security_plans → ssps; 6 index names; commentinfra/model/ssp/ssp_control_implementation.py — table + 1 FK stringinfra/model/ssp/ssp_system_characteristic.py — table + 1 FK stringinfra/model/ssp/ssp_control_implementation_objective.py — 2 FK strings (table itself unchanged)infra/model/base/oscal_component.py — 1 FK string + commentinfra/model/base/oscal_inventory_item.py — 1 FK string + commentinfra/model/base/oscal_leveraged_authorization.py — 1 FK string + commentBE main project (~/Projects/Billows/Audit-Manager/compliance-manager-be/):
infra/associations/model/project_system_characteristic.py — 1 FK string + commentapp/module_frame/service/module_frame_template_copy_service.py — 4 raw-SQL table references (RUNTIME-BREAKING)scripts/smoke_test_ssp_docx_parser.py — 2 raw-SQL references (RUNTIME — runs against live DB; fix with the rename, NOT deferred)pyproject.toml — dev path-dep toggle (revert before final commit)scripts/sql/2026-06-10-ssp-table-rename.sql — new migrationdocs/, test assertions, markdown docs.Files:
compliance-manager-be/pyproject.tomlRun: grep -n "jedi-oscal" /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/pyproject.toml Expected (current reality): a PINNED entry in the PEP 621 [project] dependencies array — "jedi-oscal==0.0.21" (around line 80) — AND a COMMENTED path override under [tool.poetry.dev-dependencies] — #jedi-oscal = { path = ".../jedi-oscal", develop = true} (around line 95).
(a) Comment out the pinned array entry (line ~80): "jedi-oscal==0.0.21", → # "jedi-oscal==0.0.21", # dev: using path override below (b) Uncomment the dev-dependency path line (line ~95): remove the leading # from #jedi-oscal = { path = "/Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-oscal", develop = true}
(The path override MUST stay under the [tool.poetry.dev-dependencies] table — don't relocate it.)
Run: cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be && poetry update jedi-oscal Expected: resolves to the local path. (Use poetry update, never poetry lock — see CLAUDE.md.)
Run: python -c "import jedi_oscal, os; print(os.path.realpath(jedi_oscal.__file__))" Expected: a path under ~/Projects/Jedicogy/.../jedi-oscal/, NOT site-packages.
ssp.pyFiles:
jedi-oscal/jedi_oscal/infra/model/ssp/ssp.py:36-45Old → New (exact string edits):
__tablename__ = "system_security_plans" → __tablename__ = "ssps"Index("ix_system_security_plans_uid", "uid") → Index("ix_ssps_uid", "uid")Index("ix_system_security_plans_profile_id", "profile_id") → Index("ix_ssps_profile_id", "profile_id")Index("ix_system_security_plans_status", "status") → Index("ix_ssps_status", "status")Index("ix_system_security_plans_metadata_id", "metadata_id") → Index("ix_ssps_metadata_id", "metadata_id")Index("ix_system_security_plans_document_id", "document_id") → Index("ix_ssps_document_id", "document_id")Index("ix_system_security_plans_group_id", "group_id") → Index("ix_ssps_group_id", "group_id")Leave unchanged: UniqueConstraint(..., name="uq_ssp_group_version") (already ssp-prefixed), the {"schema": "oscal", ...} dict, the Python class name OscalSystemSecurityPlan (class name is NOT the table name — leave it to avoid a giant cross-codebase rename out of scope).
Run: grep -n "system_security_plans" /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/infra/model/ssp/ssp.py Expected: zero hits (the TYPE_CHECKING imports reference class names / module paths, not the table string — confirm none are the literal table name).
system_security_plan_control_implementations table + FK in ssp_control_implementation.pyFiles:
jedi-oscal/jedi_oscal/infra/model/ssp/ssp_control_implementation.py:36,68Leave unchanged: uq_ssp_ctrl_impl_ssp_control_identifier, all ix_ssp_ctrl_impl_* indexes (already ssp-prefixed), the FK column system_security_plan_id.
system_security_plans_system_characteristics table + FK in ssp_system_characteristic.pyFiles:
jedi-oscal/jedi_oscal/infra/model/ssp/ssp_system_characteristic.py:23,48Leave unchanged: all ix_ssp_system_characteristics_* indexes (already correct), the FK column system_security_plan_id.
ssp_control_implementation_objective.pyFiles:
jedi-oscal/jedi_oscal/infra/model/ssp/ssp_control_implementation_objective.py:59,64(NOTE: this table ssp_control_implementation_objectives is NOT renamed — only its two FK targets are.)
Files:
jedi-oscal/jedi_oscal/infra/model/base/oscal_component.py:91jedi-oscal/jedi_oscal/infra/model/base/oscal_inventory_item.py:86jedi-oscal/jedi_oscal/infra/model/base/oscal_leveraged_authorization.py:77Run:
grep -rn --include='*.py' -E 'system_security_plans\b|system_security_plan_control_implementations\b|system_security_plans_system_characteristics\b' /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/Expected: zero hits. (The Python class name OscalSystemSecurityPlan has no trailing space-bounded table string, so \b on system_security_plans won't match it — but if any hit appears, inspect: class names stay, table strings go.)
grep -rn -E 'system_security_plan_control_implementations|system_security_plans_system_characteristics|"oscal\.system_security_plans' /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/ --include='*.py' | grep -v '/jedi-oscal/'Expected: zero hits.
cd /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-oscal
git add jedi_oscal/infra/model/ssp/ssp.py jedi_oscal/infra/model/ssp/ssp_control_implementation.py jedi_oscal/infra/model/ssp/ssp_system_characteristic.py jedi_oscal/infra/model/ssp/ssp_control_implementation_objective.py jedi_oscal/infra/model/base/oscal_component.py jedi_oscal/infra/model/base/oscal_inventory_item.py jedi_oscal/infra/model/base/oscal_leveraged_authorization.py
git commit -m "refactor(ssp): rename system_security_plans* tables to ssp(s)_*"(Do NOT push / bump version / publish to Nexus yet — that's Phase 6.)
project_system_characteristic.pyFiles:
compliance-manager-be/infra/associations/model/project_system_characteristic.py:45,49Leave unchanged: the column system_characteristic_id, the imported class OscalSystemSecurityPlanSystemCharacteristic (class name, not table).
module_frame_template_copy_service.py (RUNTIME-BREAKING)Files:
compliance-manager-be/app/module_frame/service/module_frame_template_copy_service.py:75,321,327,347(The column lists inside these statements — system_security_plan_id, etc. — are columns, NOT table names. Leave them.)
FROM oscal.system_security_plan_control_implementations → FROM oscal.ssp_control_implementationsSELECT id FROM oscal.system_security_plans WHERE uid = %s → SELECT id FROM oscal.ssps WHERE uid = %sRun:
grep -rn --include='*.py' -E 'oscal\.system_security_plans\b|oscal\.system_security_plan_control_implementations|oscal\.system_security_plans_system_characteristics' /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/api /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/app /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/domain /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/infraExpected: zero hits inside executable SQL / text(...) / ForeignKey(...) strings. A handful of remaining hits will be PROSE ONLY (comments / docstrings) — these are acceptable here and cleaned in Phase 6 Task 19. Known prose-only survivors (verify each is a comment/docstring, not code): app/oscal/service/ssp_excel_import_app_service.py:970,2168, app/oscal/service/ssp_versioning_service.py:4-5, domain/oscal/service/ssp_shell_service.py:124, infra/module_frame/models/module_frame_control_default.py:22, infra/grc/model/ssp_reference_document_mapping.py:15. If a hit is anything other than a comment/docstring, STOP and fix it now.
Files:
compliance-manager-be/scripts/sql/2026-06-10-ssp-table-rename.sql-- Date: 2026-06-10
-- Purpose: Rename verbose SSP tables to short ssp(s)_* names (table-only; columns unchanged).
-- system_security_plans -> ssps
-- system_security_plan_control_implementations -> ssp_control_implementations
-- system_security_plans_system_characteristics -> ssp_system_characteristics
-- In-place ALTER ... RENAME: data preserved, FK integrity auto-followed by PostgreSQL.
-- Run with: psql --single-transaction -v ON_ERROR_STOP=1 -f <this file> (account: cmmgr)
-- ===== 1. Tables =====
ALTER TABLE oscal.system_security_plans RENAME TO ssps; -- 2026-06-10
ALTER TABLE oscal.system_security_plan_control_implementations RENAME TO ssp_control_implementations; -- 2026-06-10
ALTER TABLE oscal.system_security_plans_system_characteristics RENAME TO ssp_system_characteristics; -- 2026-06-10
-- ===== 2. Sequences (serial id owners) =====
ALTER SEQUENCE oscal.system_security_plans_id_seq RENAME TO ssps_id_seq; -- 2026-06-10
ALTER SEQUENCE oscal.system_security_plan_control_implementations_id_seq RENAME TO ssp_control_implementations_id_seq; -- 2026-06-10
ALTER SEQUENCE oscal.system_security_plans_system_characteristics_id_seq RENAME TO ssp_system_characteristics_id_seq; -- 2026-06-10
-- ===== 3. Primary keys =====
ALTER TABLE oscal.ssps RENAME CONSTRAINT system_security_plans_pkey TO ssps_pkey; -- 2026-06-10
ALTER TABLE oscal.ssp_control_implementations RENAME CONSTRAINT system_security_plan_control_implementations_pkey TO ssp_control_implementations_pkey; -- 2026-06-10
ALTER TABLE oscal.ssp_system_characteristics RENAME CONSTRAINT system_security_plans_system_characteristics_pkey TO ssp_system_characteristics_pkey; -- 2026-06-10
-- ===== 4. Unique (uid) keys =====
ALTER TABLE oscal.ssps RENAME CONSTRAINT system_security_plans_uid_key TO ssps_uid_key; -- 2026-06-10
ALTER TABLE oscal.ssp_control_implementations RENAME CONSTRAINT system_security_plan_control_implementations_uid_key TO ssp_control_implementations_uid_key; -- 2026-06-10
ALTER TABLE oscal.ssp_system_characteristics RENAME CONSTRAINT system_security_plans_system_characteristics_uid_key TO ssp_system_characteristics_uid_key; -- 2026-06-10
-- ===== 5. Indexes on ssps (the only table whose ix_* embedded the old name) =====
ALTER INDEX oscal.ix_system_security_plans_uid RENAME TO ix_ssps_uid; -- 2026-06-10
ALTER INDEX oscal.ix_system_security_plans_profile_id RENAME TO ix_ssps_profile_id; -- 2026-06-10
ALTER INDEX oscal.ix_system_security_plans_status RENAME TO ix_ssps_status; -- 2026-06-10
ALTER INDEX oscal.ix_system_security_plans_metadata_id RENAME TO ix_ssps_metadata_id; -- 2026-06-10
ALTER INDEX oscal.ix_system_security_plans_document_id RENAME TO ix_ssps_document_id; -- 2026-06-10
ALTER INDEX oscal.ix_system_security_plans_group_id RENAME TO ix_ssps_group_id; -- 2026-06-10
-- (ix_ssp_ctrl_impl_* and ix_ssp_system_characteristics_* are already ssp-prefixed; no rename.)
-- ===== 6. FK constraints whose auto-generated name embedded the old table name =====
-- Outgoing FKs on ssps:
ALTER TABLE oscal.ssps RENAME CONSTRAINT system_security_plans_document_id_fkey TO ssps_document_id_fkey; -- 2026-06-10
ALTER TABLE oscal.ssps RENAME CONSTRAINT system_security_plans_metadata_id_fkey TO ssps_metadata_id_fkey; -- 2026-06-10
ALTER TABLE oscal.ssps RENAME CONSTRAINT system_security_plans_profile_id_fkey TO ssps_profile_id_fkey; -- 2026-06-10
-- Child->parent FKs (name was truncated from old child table name):
ALTER TABLE oscal.ssp_control_implementations RENAME CONSTRAINT system_security_plan_control_imple_system_security_plan_id_fkey TO ssp_control_implementations_system_security_plan_id_fkey; -- 2026-06-10
ALTER TABLE oscal.ssp_system_characteristics RENAME CONSTRAINT system_security_plans_system_chara_system_security_plan_id_fkey TO ssp_system_characteristics_system_security_plan_id_fkey; -- 2026-06-10
-- (FKs on ssp_components / ssp_inventory_items / ssp_leveraged_authorizations / ssp_control_implementation_objectives
-- use column ssp_id / control_implementation_id in their auto-name, NOT the renamed parent table name → no rename.)
-- ===== 7. Cross-schema FK from compliance.project_system_characteristic_mapping =====
-- Auto-follows the rename (references by OID); its constraint name embeds the CHILD table + column,
-- not the renamed parent, so no rename needed. Verified in Step 3 below.
-- ===== 8. Record migration =====
INSERT INTO public.schema_migrations (version, description, applied_at)
VALUES ('2026-06-10-ssp-table-rename', 'Rename system_security_plans* tables to ssp(s)_*', now())
ON CONFLICT DO NOTHING; -- 2026-06-10NOTE: confirm the exact
schema_migrationscolumn signature before finalizing —grep -rn "INSERT.*schema_migrations" scripts/sql/ | tail -3and copy the latest pattern (it may be(version)only).
PGPASSWORD=<查.env> psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "
SELECT conname FROM pg_constraint
WHERE conrelid IN ('oscal.system_security_plans'::regclass,
'oscal.system_security_plan_control_implementations'::regclass,
'oscal.system_security_plans_system_characteristics'::regclass)
ORDER BY 1;"Reconcile any mismatch between this output and the constraint names in the migration. If a name differs, fix the migration (NOT the DB).
PGPASSWORD=<查.env> psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -v ON_ERROR_STOP=1 <<'EOF'
BEGIN;
\i /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-06-10-ssp-table-rename.sql
-- confirm new names exist within the txn:
SELECT relname FROM pg_class WHERE relname IN ('ssps','ssp_control_implementations','ssp_system_characteristics') ORDER BY 1;
ROLLBACK;
EOFExpected: every ALTER succeeds, the SELECT returns the three new names, then ROLLBACK leaves DB untouched. Any error → fix migration, re-run. Do NOT proceed to Phase 4 until this is clean.
PGPASSWORD=<查.env> psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev --single-transaction -v ON_ERROR_STOP=1 -f /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-06-10-ssp-table-rename.sqlExpected: COMMIT, no errors.
PGPASSWORD=<查.env> psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "
SELECT 'ssps', count(*) FROM oscal.ssps
UNION ALL SELECT 'ssp_control_implementations', count(*) FROM oscal.ssp_control_implementations
UNION ALL SELECT 'ssp_system_characteristics', count(*) FROM oscal.ssp_system_characteristics;"Expected: ~181 / ~2312 / ~180 (data preserved). Old names should now error if queried.
PGPASSWORD=<查.env> psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "
SELECT conname, convalidated FROM pg_constraint
WHERE contype='f' AND (confrelid='oscal.ssps'::regclass
OR conrelid IN ('oscal.ssps'::regclass,'oscal.ssp_control_implementations'::regclass,'oscal.ssp_system_characteristics'::regclass));"Expected: all convalidated = t.
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
pytest test/test_module_frame_template_copy_service.py tests/test_module_frame_template_copy_service_phase2.py test/test_oscal_project_service_template_copy_integration.py -vExpected: PASS. Note: these tests have assertions matching the OLD table names (scoping flagged test/test_module_frame_template_copy_service.py:73, integration test lines 56/59/306/325/348/361, phase2 lines 41/55). They WILL fail until updated — update those assertions to the new names as part of this step, then re-run to green. (This is the "failing test → fix → pass" loop for the rename.)
⚠️ Substring-collision trap when updating assertions — do these replacements by exact full-string, NOT a blind sed:
system_security_plans (table) → ssps, BUT the column system_security_plan_id (singular _plan_id, no trailing s) must stay. A s/system_security_plans/ssps/g is safe (column has no s after plan); a s/system_security_plan/ssp/g would CORRUPT the column — never use the singular form.system_security_plans_system_characteristics → ssp_system_characteristics must run BEFORE / be matched more-specifically than system_security_plans → ssps (the latter is a substring of the former). Replace the longest names first.test_oscal_project_service_template_copy_integration.py which mixes table strings (L59/325/361) and column strings (L134/140/473/485/518/523) in one file.cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
git add infra/associations/model/project_system_characteristic.py app/module_frame/service/module_frame_template_copy_service.py scripts/sql/2026-06-10-ssp-table-rename.sql test/test_module_frame_template_copy_service.py tests/test_module_frame_template_copy_service_phase2.py test/test_oscal_project_service_template_copy_integration.py
git commit -m "refactor(ssp): rename system_security_plans* tables to ssp(s)_* (BE code + migration)"(Do NOT include the pyproject.toml path-dep change. Do NOT push — user pushes.)
🛑 CHECKPOINT — STOP HERE. Per CLAUDE.md, fix/change commits stop before closing. Give the user a one-line status + the manual smoke checklist. Do NOT auto-run Phase 5/6. Wait for explicit user command ("STG 套下去" / "發版" / "收尾").
Each env = apply migration + deploy code that runs the new ORM, in lockstep. STG & DEV share server 188 (different DB). POC = 189. PROD per deploy docs. ⚠️ App and DB MUST move together per env — old app + renamed table crashes (see
feedback_cross_schema_fk_must_qualify,feedback_rebuild_docker_image_after_schema_move).
| Risk | Mitigation |
|---|---|
| Old app hits renamed table (or vice-versa) → 500 / NoReferencedTableError | Lockstep: migration + new-ORM deploy together per env (Task 14, Phase 5) |
| Hardcoded constraint/index names differ per env (created at different times) | Task 11 Step 2 + Task 15/16 re-verify names from catalog before each env |
| Missed raw-SQL reference (grep-invisible at runtime) | Task 10 grep gate scoped to api/app/domain/infra; manual e2e smoke (Task 14 Step 4) |
schema_migrations insert shape wrong |
Task 11 Step 1 NOTE: copy latest existing INSERT pattern |
| jedi-oscal used by an unknown downstream consumer | Scoping confirmed zero refs in the monorepo; if external consumers exist, they pin old version until they upgrade (table rename is a breaking schema change — call it out in the version bump notes) |
| Accidentally renaming a column | Scope Boundaries section: columns explicitly excluded; reviewer must check no column edits |