Skip to content

Commit 50cce65

Browse files
authored
Workflow stamp: record every declared schema for top-hash parity (#773)
1 parent bd3e4f0 commit 50cce65

20 files changed

Lines changed: 483 additions & 133 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/verification.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ split -l 1 ~/MANIFEST.jsonl \
4141
tr -d '\n' | sha256sum
4242
```
4343

44+
**Caveat**: Use this meta-null form only when rows actually carry
45+
`"meta": null`. Applied to a header line (which has no `meta`), `jq` reads the
46+
absent `.meta` as null and injects `meta: {}`, corrupting the header hash — use
47+
the plain `del(.physical_keys)` form otherwise.
48+
4449
## CRC64/NVMe Verification
4550

4651
CRC64-NVMe is a whole-file checksum (no chunking). The digest is 8 bytes,

quilt-cli/src/cli/push.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ mod tests {
171171
use quilt_rs::manifest::WorkflowId;
172172
use quilt_rs::paths::DomainPaths;
173173
use quilt_uri::S3Uri;
174+
use std::collections::BTreeMap;
174175
use tempfile::TempDir;
175176

176177
let home_dir = TempDir::new()?;
@@ -234,7 +235,7 @@ mod tests {
234235
config: config_uri.to_string().parse()?,
235236
id: Some(WorkflowId {
236237
id: "gate".to_string(),
237-
metadata: None,
238+
schemas: BTreeMap::new(),
238239
}),
239240
};
240241
package

quilt-rs/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
<!-- markdownlint-disable MD013 -->
1010
# Changelog
1111

12+
## [v0.33.0-alpha10] - 2026-07-14
13+
14+
### Fixed
15+
16+
- The workflow provenance stamp now records every schema a workflow declares, not only its metadata schema, so a package committed under an entries-schema workflow gets the same top-hash as the Python client (<https://github.com/quiltdata/quilt-rs/pull/773>)
17+
1218
## [v0.33.0-alpha9] - 2026-07-13
1319

1420
### Added

quilt-rs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "quilt-rs"
33
description = "Rust library for accessing Quilt data packages."
44

55
# Inherit from workspace
6-
version = "0.33.0-alpha9"
6+
version = "0.33.0-alpha10"
77
edition.workspace = true
88
rust-version.workspace = true
99
license.workspace = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"message":"","user_meta":{},"version":"v0","workflow":{"config":"s3://workflow/config","id":"dual-wf","schemas":{"meta-schema":"s3://bucket/workflows/meta.json","entries-schema":"s3://bucket/workflows/entries.json"}}}
2+
{"logical_key":"test1.txt","physical_keys":["s3://bucket/test1.txt"],"hash":{"type":"sha2-256-chunked","value":"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="},"size":0,"meta":{}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"version":"v0","message":"","user_meta":{},"workflow":{"id":"dual-wf","config":"s3://workflow/config","schemas":{"entries-schema":"s3://bucket/workflows/entries.json","meta-schema":"s3://bucket/workflows/meta.json"}}}
2+
{"logical_key":"test1.txt","physical_keys":["s3://bucket/test1.txt"],"hash":{"type":"sha2-256-chunked","value":"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="},"size":0,"meta":{}}

quilt-rs/src/fixtures/top_hash.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ pub const EMPTY_EMPTY_SIMPLE_WORKFLOW_TOP_HASH: &str =
8484
pub const EMPTY_EMPTY_COMPLEX_WORKFLOW_TOP_HASH: &str =
8585
"714b1c209a98a7b9239076b94305a7852dc60946c5ba0afac64246ea9958ba08";
8686

87+
/// Manifest header: `{"message":"","user_meta":{},"version":"v0","workflow":{"config":"s3://workflow/config","id":"entries-wf","schemas":{"entries-schema":"s3://bucket/workflows/entries.json"}}}`
88+
///
89+
/// A workflow that declares only an `entries_schema` — the stamp the old
90+
/// single-schema model dropped entirely. Corroborated against quilt3's
91+
/// `_calculate_top_hash` on the identical header.
92+
pub const EMPTY_EMPTY_ENTRIES_WORKFLOW_TOP_HASH: &str =
93+
"30804452ca34818546a752710397e6d2102d2683e12b67b53cfa9eac3fc83439";
94+
95+
/// Manifest header: `{"message":"","user_meta":{},"version":"v0","workflow":{"config":"s3://workflow/config","id":"dual-wf","schemas":{"entries-schema":"s3://bucket/workflows/entries.json","meta-schema":"s3://bucket/workflows/meta.json"}}}`
96+
///
97+
/// A workflow declaring both a `metadata_schema` and an `entries_schema`: the
98+
/// stamp carries both content addresses. Corroborated against quilt3's
99+
/// `_calculate_top_hash` on the identical header.
100+
pub const EMPTY_EMPTY_DUAL_WORKFLOW_TOP_HASH: &str =
101+
"93add62524fc06c6aa17c072f5370bfc96350257f253010c6bf688f890277178";
102+
87103
/// Manifest header: `{"message":"Initial","user_meta":{},"version":"v0","workflow":{"config":"s3://workflow/config","id":null}}`
88104
pub const INITIAL_EMPTY_SIMPLE_WORKFLOW_TOP_HASH: &str =
89105
"c716c54535bd3c896d0813dafd672430456f68b2d407a6a65a558ccab53f4990";
@@ -156,3 +172,11 @@ pub const MIXED_HASH_TYPES_TOP_HASH: &str =
156172
/// ```
157173
pub const NORMALIZED_EQUIVALENCE_TOP_HASH: &str =
158174
"10c3b62176b4fbb25b4988181bb65e3861087403f36f13c8adb66bce52d6471b";
175+
176+
/// Cross-client equivalence for the workflow stamp's `schemas` map: two manifests
177+
/// carrying a dual-schema stamp with the `schemas` keys (and header field order)
178+
/// written differently must address identically, because both clients sort keys
179+
/// before hashing. Variants: `workflow-schemas-canonical`, `workflow-schemas-key-order`.
180+
/// Corroborated against quilt3's `_calculate_top_hash` on the header + row.
181+
pub const WORKFLOW_SCHEMAS_EQUIVALENCE_TOP_HASH: &str =
182+
"b5af547f5ea5f6d7c878cb39592ce1fe0582958f1096b60c1c6c76d19fb5f936";

quilt-rs/src/flow/commit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ mod tests {
390390
config: config_uri.clone(),
391391
id: Some(WorkflowId {
392392
id: "gate".to_string(),
393-
metadata: None,
393+
schemas: BTreeMap::new(),
394394
}),
395395
};
396396

quilt-rs/src/flow/publish.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,6 @@ mod tests {
546546
/// doc (2×), compiling identical validators twice.
547547
#[test(tokio::test)]
548548
async fn test_publish_fetches_config_and_schema_once() -> Res {
549-
use crate::manifest::MetadataSchema;
550549
use crate::manifest::Workflow;
551550
use crate::manifest::WorkflowId;
552551

@@ -581,10 +580,7 @@ mod tests {
581580
config: config_uri.parse()?,
582581
id: Some(WorkflowId {
583582
id: "gate".to_string(),
584-
metadata: Some(MetadataSchema {
585-
id: "meta".to_string(),
586-
url: schema_uri.parse()?,
587-
}),
583+
schemas: BTreeMap::from([("meta".to_string(), schema_uri.parse()?)]),
588584
}),
589585
};
590586

0 commit comments

Comments
 (0)