Skip to content

Commit 21b7f89

Browse files
fix(v3): repair native decoder and authenticate existing family upgrades (#101)
Native rooted plan admission failed because PL/pgSQL parent and child locals conflicted with unqualified graph-query columns. Qualify both columns and repair the migration shape sampler to deparse under pg_catalog,pg_temp with parameterized caller-path restoration. Share trusted forward migration logic between 400 and new 500: authenticate exact 6d/900e/current implementation, captured historical templates, authority/full physical catalogs, scope and canonical namespace identity; replace the decoder on old 6d and the three changed functions on 900e while preserving its relations, function OIDs, reader high-water and retained ownership/history. Admit the exact known Q-visible legacy deparse shape only for the authenticated 900e policy with no actual Q. Handle the legitimate 900e state with a missing 400 ledger without replaying reader DDL. Move the old reader capture into migration verification and reuse the forward-only template renderer in owned fixtures. Add six PostgreSQL regression sources for active/terminal readers, nonzero issuance, all anchors/OIDs, old SID lookup, Q-less canonical/known legacy policies, missing ledger, tampering and idempotence. Add test-only underlying install error output; the production 503 remains unchanged. Owned nightly formatting, candidate whitespace, historical-source equivalence and patch checks pass. Native build/tests/Clippy, PostgreSQL execution and performance measurements remain pending; this is a source-only submission.
1 parent cc90da9 commit 21b7f89

12 files changed

Lines changed: 1254 additions & 437 deletions
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
use super::*;
2+
use crate::jupiter::{
3+
migration::{apply_migrations, test_upgrade_native_runtime},
4+
storage::qualified_metadata_family::reader_previous_fixture::restore_retention,
5+
};
6+
7+
async fn retained_owners(fixture: &Fixture) -> Value {
8+
let txn = transaction(fixture).await;
9+
let value = txn.query_one_raw(Statement::from_string(DbBackend::Postgres,
10+
"SELECT jsonb_build_object(
11+
'readers',(SELECT jsonb_agg(to_jsonb(x) ORDER BY operation_id,reader_issuance) FROM mst2_metadata_reader_operation x),
12+
'anchors',(SELECT jsonb_agg(to_jsonb(x) ORDER BY anchor_id) FROM mst2_metadata_root_anchor x),
13+
'issuance',(SELECT to_jsonb(x) FROM mst2_metadata_reader_issuance x),
14+
'functions',(SELECT jsonb_agg(jsonb_build_array(p.proname,p.oid::bigint) ORDER BY p.proname,p.oid)
15+
FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid=p.pronamespace
16+
WHERE n.nspname=current_schema() OR (n.nspname=(SELECT c.nspname FROM mst2_metadata_family_identity i
17+
JOIN pg_catalog.pg_namespace c ON c.oid=i.core_schema_oid LIMIT 1)
18+
AND p.proname='mst2_route_family_registration_guard'))) AS owners"))
19+
.await.unwrap().unwrap().try_get("", "owners").unwrap();
20+
txn.commit().await.unwrap();
21+
value
22+
}
23+
24+
async fn policy(core: &sea_orm::DatabaseConnection) -> Value {
25+
core.query_one_raw(Statement::from_string(
26+
DbBackend::Postgres,
27+
"SELECT to_jsonb(p) FROM mst2_qualified_family_policy p WHERE singleton=1",
28+
))
29+
.await
30+
.unwrap()
31+
.unwrap()
32+
.try_get_by_index(0)
33+
.unwrap()
34+
}
35+
36+
async fn seed_live_and_terminal(fixture: &Fixture) {
37+
let txn = transaction(fixture).await;
38+
admission(&txn, fixture).await;
39+
txn.commit().await.unwrap();
40+
let txn = transaction(fixture).await;
41+
let terminal = admission(&txn, fixture).await;
42+
finish(&txn, &terminal).await;
43+
txn.commit().await.unwrap();
44+
assert_eq!(
45+
q_count(
46+
fixture,
47+
"SELECT count(*) FROM {q}.mst2_metadata_reader_operation WHERE state='ACTIVE'"
48+
)
49+
.await,
50+
1
51+
);
52+
assert_eq!(
53+
q_count(
54+
fixture,
55+
"SELECT count(*) FROM {q}.mst2_metadata_reader_operation WHERE state='FINISHED'"
56+
)
57+
.await,
58+
1
59+
);
60+
assert!(
61+
q_count(
62+
fixture,
63+
"SELECT high_water FROM {q}.mst2_metadata_reader_issuance"
64+
)
65+
.await
66+
>= 2
67+
);
68+
}
69+
70+
async fn assert_old_sid_works(fixture: &Fixture) {
71+
let pinned = fixture
72+
.state
73+
.storage
74+
.snapshot_context(&fixture.snapshot, &fixture.lease)
75+
.await
76+
.unwrap();
77+
let repository = RootedQualifiedMetadataRepository::open(
78+
fixture.state.storage.mono_storage().get_connection(),
79+
&fixture.state.storage.config().database,
80+
)
81+
.await
82+
.unwrap();
83+
let context = repository
84+
.context(&fixture.snapshot, &fixture.lease, &pinned.built.instance_id)
85+
.await
86+
.unwrap();
87+
assert_eq!(context.built.descriptor, pinned.built.descriptor);
88+
assert_eq!(context.commit_oid, pinned.commit_oid);
89+
let lookup = repository
90+
.lookup_metadata(&context, &["/file".to_owned()])
91+
.await
92+
.unwrap();
93+
assert!(matches!(
94+
lookup.results.as_slice(),
95+
[RootedLookupStatus::File { .. }]
96+
));
97+
fixture.counts.assert(0, 0);
98+
}
99+
100+
#[tokio::test]
101+
async fn captured_cc90_upgrade_preserves_nonzero_reader_issuance_all_owners_oids_and_old_sid() {
102+
let fixture = Fixture::new_with_pg_config(true).await;
103+
seed_live_and_terminal(&fixture).await;
104+
let schema = q_schema(&fixture).await;
105+
let mono = fixture.state.storage.mono_storage();
106+
let core = mono.get_connection();
107+
let rows = permanent_rows(&fixture).await;
108+
let owners = retained_owners(&fixture).await;
109+
restore_retention(core, &schema, true, false).await;
110+
assert_eq!(retained_owners(&fixture).await, owners);
111+
test_upgrade_native_runtime(core).await.unwrap();
112+
assert_eq!(permanent_rows(&fixture).await, rows);
113+
assert_eq!(retained_owners(&fixture).await, owners);
114+
let stamp = policy(core).await;
115+
test_upgrade_native_runtime(core).await.unwrap();
116+
assert_eq!(policy(core).await, stamp);
117+
assert_eq!(retained_owners(&fixture).await, owners);
118+
assert_eq!(permanent_rows(&fixture).await, rows);
119+
assert_old_sid_works(&fixture).await;
120+
assert_eq!(permanent_rows(&fixture).await, rows);
121+
provision_or_verify_rooted_qualified_family(core)
122+
.await
123+
.unwrap();
124+
}
125+
126+
#[tokio::test]
127+
async fn captured_cc90_interrupted_before_reader_migration_resumes_without_reader_ddl() {
128+
let fixture = Fixture::new_with_pg_config(true).await;
129+
seed_live_and_terminal(&fixture).await;
130+
let schema = q_schema(&fixture).await;
131+
let mono = fixture.state.storage.mono_storage();
132+
let core = mono.get_connection();
133+
restore_retention(core, &schema, true, false).await;
134+
let rows = permanent_rows(&fixture).await;
135+
let owners = retained_owners(&fixture).await;
136+
core.execute_raw(Statement::from_sql_and_values(
137+
DbBackend::Postgres,
138+
"DELETE FROM seaql_migrations WHERE version IN ($1,$2)",
139+
[
140+
"m20261008_000400_add_mst2_reader_retention".into(),
141+
"m20261008_000500_fix_mst2_native_runtime".into(),
142+
],
143+
))
144+
.await
145+
.unwrap();
146+
apply_migrations(core, false).await.unwrap();
147+
assert_eq!(permanent_rows(&fixture).await, rows);
148+
assert_eq!(retained_owners(&fixture).await, owners);
149+
let count: i64 = core.query_one_raw(Statement::from_string(DbBackend::Postgres,
150+
"SELECT count(*) FROM seaql_migrations WHERE version IN
151+
('m20261008_000400_add_mst2_reader_retention','m20261008_000500_fix_mst2_native_runtime')"))
152+
.await.unwrap().unwrap().try_get_by_index(0).unwrap();
153+
assert_eq!(count, 2);
154+
assert_old_sid_works(&fixture).await;
155+
}
156+
157+
async fn qless_upgrade(legacy: bool) {
158+
let temp = tempfile::tempdir().unwrap();
159+
let (config, _schema) = test_db_config(temp.path()).await;
160+
let core = crate::jupiter::storage::init::database_connection(&config)
161+
.await
162+
.unwrap();
163+
let old_schema: String = core
164+
.query_one_raw(Statement::from_string(
165+
DbBackend::Postgres,
166+
"SELECT metadata_schema FROM mst2_metadata_namespace WHERE graph_domain='qualified-v1'",
167+
))
168+
.await
169+
.unwrap()
170+
.unwrap()
171+
.try_get_by_index(0)
172+
.unwrap();
173+
restore_retention(&core, &old_schema, false, legacy).await;
174+
test_upgrade_native_runtime(&core).await.unwrap();
175+
let stamp = policy(&core).await;
176+
test_upgrade_native_runtime(&core).await.unwrap();
177+
assert_eq!(policy(&core).await, stamp);
178+
provision_or_verify_rooted_qualified_family(&core)
179+
.await
180+
.unwrap();
181+
provision_or_verify_rooted_qualified_family(&core)
182+
.await
183+
.unwrap();
184+
let row = core
185+
.query_one_raw(Statement::from_string(
186+
DbBackend::Postgres,
187+
"SELECT metadata_schema FROM mst2_metadata_namespace WHERE graph_domain='qualified-v1'",
188+
))
189+
.await
190+
.unwrap()
191+
.unwrap();
192+
let schema: String = row.try_get_by_index(0).unwrap();
193+
assert_ne!(schema, old_schema);
194+
let water: i64 = core
195+
.query_one_raw(Statement::from_string(
196+
DbBackend::Postgres,
197+
format!(
198+
"SELECT high_water FROM \"{}\".mst2_metadata_reader_issuance",
199+
schema.replace('"', "\"\"")
200+
),
201+
))
202+
.await
203+
.unwrap()
204+
.unwrap()
205+
.try_get_by_index(0)
206+
.unwrap();
207+
assert_eq!(water, 0);
208+
}
209+
210+
#[tokio::test]
211+
async fn captured_cc90_qless_canonical_policy_upgrades_before_new_provisioning() {
212+
qless_upgrade(false).await;
213+
}
214+
215+
#[tokio::test]
216+
async fn captured_cc90_qless_exact_legacy_deparse_policy_upgrades_before_new_provisioning() {
217+
qless_upgrade(true).await;
218+
}
219+
220+
#[tokio::test]
221+
async fn captured_cc90_tampered_decoder_rejects_upgrade_without_rewriting_owners_or_policy() {
222+
let fixture = Fixture::new_with_pg_config(true).await;
223+
seed_live_and_terminal(&fixture).await;
224+
let schema = q_schema(&fixture).await;
225+
let mono = fixture.state.storage.mono_storage();
226+
let core = mono.get_connection();
227+
restore_retention(core, &schema, true, false).await;
228+
let before = policy(core).await;
229+
let owners = retained_owners(&fixture).await;
230+
let rows = permanent_rows(&fixture).await;
231+
core.execute_unprepared(&format!(
232+
"CREATE OR REPLACE FUNCTION \"{}\".mst2_metadata_decode_rooted_plan(b bytea) RETURNS jsonb
233+
LANGUAGE plpgsql IMMUTABLE STRICT AS $$ BEGIN RAISE EXCEPTION 'tampered decoder'; END $$",
234+
schema.replace('"', "\"\""))).await.unwrap();
235+
assert!(test_upgrade_native_runtime(core).await.is_err());
236+
assert_eq!(policy(core).await, before);
237+
assert_eq!(retained_owners(&fixture).await, owners);
238+
assert_eq!(permanent_rows(&fixture).await, rows);
239+
}
240+
241+
#[tokio::test]
242+
async fn captured_cc90_qless_arbitrary_policy_shape_is_never_resigned() {
243+
for legacy in [false, true] {
244+
let temp = tempfile::tempdir().unwrap();
245+
let (config, _schema) = test_db_config(temp.path()).await;
246+
let core = crate::jupiter::storage::init::database_connection(&config)
247+
.await
248+
.unwrap();
249+
let schema: String = core.query_one_raw(Statement::from_string(DbBackend::Postgres,
250+
"SELECT metadata_schema FROM mst2_metadata_namespace WHERE graph_domain='qualified-v1'"))
251+
.await.unwrap().unwrap().try_get_by_index(0).unwrap();
252+
restore_retention(&core, &schema, false, legacy).await;
253+
core.execute_unprepared("ALTER TABLE mst2_qualified_family_policy DISABLE TRIGGER mst2_route_family_policy_immutable;
254+
UPDATE mst2_qualified_family_policy SET expected_shape=decode(repeat('a5',32),'hex') WHERE singleton=1;
255+
ALTER TABLE mst2_qualified_family_policy ENABLE TRIGGER mst2_route_family_policy_immutable").await.unwrap();
256+
let before = policy(&core).await;
257+
let schemas: Value = core.query_one_raw(Statement::from_string(DbBackend::Postgres,
258+
"SELECT coalesce(jsonb_agg(n.nspname ORDER BY n.nspname),'[]'::jsonb) FROM pg_catalog.pg_namespace n
259+
WHERE EXISTS(SELECT 1 FROM pg_catalog.pg_proc p WHERE p.pronamespace=n.oid
260+
AND p.proname='mst2_metadata_dml_barrier'
261+
AND strpos(p.prosrc,chr(34)||current_schema()||chr(34))>0)"))
262+
.await.unwrap().unwrap().try_get_by_index(0).unwrap();
263+
assert!(test_upgrade_native_runtime(&core).await.is_err());
264+
assert_eq!(policy(&core).await, before);
265+
let after: Value = core.query_one_raw(Statement::from_string(DbBackend::Postgres,
266+
"SELECT coalesce(jsonb_agg(n.nspname ORDER BY n.nspname),'[]'::jsonb) FROM pg_catalog.pg_namespace n
267+
WHERE EXISTS(SELECT 1 FROM pg_catalog.pg_proc p WHERE p.pronamespace=n.oid
268+
AND p.proname='mst2_metadata_dml_barrier'
269+
AND strpos(p.prosrc,chr(34)||current_schema()||chr(34))>0)"))
270+
.await.unwrap().unwrap().try_get_by_index(0).unwrap();
271+
assert_eq!(
272+
after, schemas,
273+
"rejected migration must roll back every temporary template"
274+
);
275+
}
276+
}

‎src/api/router/snapshot_reader_retention_upgrade_tests.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use crate::jupiter::{
77
},
88
};
99

10+
#[path = "snapshot_native_runtime_upgrade_tests.rs"]
11+
mod native_runtime;
12+
1013
async fn permanent_rows(fixture: &Fixture) -> Value {
1114
let txn = transaction(fixture).await;
1215
let rows=txn.query_one_raw(Statement::from_string(DbBackend::Postgres,

0 commit comments

Comments
 (0)