Skip to content

Commit efebad9

Browse files
committed
Complete Phase 9 gamma with audit bundle v3 and six-workflow CI matrix.
Unify agent verification across all workflows, isolate STAC overlay paths for tests, and document Phase 10 federated catalog roadmap.
1 parent c563f6d commit efebad9

11 files changed

Lines changed: 158 additions & 60 deletions

File tree

.github/workflows/ci.yml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,43 @@ jobs:
2121
- name: Build workspace
2222
run: cargo build --workspace
2323
- name: Test workspace
24-
run: cargo test --workspace
24+
run: cargo test --workspace -- --test-threads=1
2525
- name: MVP workflow smoke
2626
run: cargo run -p genegis-cli -- workflow run nagoya-density
2727
- name: MVP execute pipeline
2828
run: cargo run -p genegis-cli -- workflow run nagoya-density --execute
2929
- name: MVP ask intent pipeline
3030
run: cargo run -p genegis-cli -- ask "名古屋市の人口密度を表示" --no-html
31-
- name: Phase 8 gamma — agent workflow matrix
31+
- name: Phase 9 gamma — six-workflow agent matrix
3232
run: |
3333
cargo run -p genegis-cli -- agent run "名古屋市の人口密度を表示"
3434
cargo run -p genegis-cli -- agent run "ローカルCOGデモのメタデータを表示"
3535
cargo run -p genegis-cli -- agent run "リモートCOGデモのメタデータを表示"
3636
cargo run -p genegis-cli -- agent run "名古屋 wards GeoParquet を検証"
37-
- name: Phase 9 beta — discovery + GeoParquet density smoke
38-
run: |
3937
cargo run -p genegis-cli -- agent run "外部STAC examples/stac/sample-collection.json を fetch"
4038
cargo run -p genegis-cli -- agent run "名古屋 GeoParquet 人口密度を表示"
41-
cargo run -p genegis-cli -- workflow run nagoya-geoparquet-density --execute
39+
- name: Phase 9 gamma — ask + overlay smoke
40+
run: |
41+
cargo run -p genegis-cli -- ask "名古屋 GeoParquet 人口密度を表示" --no-html
4242
cargo run -p genegis-cli -- catalog stac import examples/stac/sample-item.json
43+
cargo run -p genegis-cli -- workflow run nagoya-geoparquet-density --execute
4344
- name: Agent human gate smoke
4445
run: |
4546
cargo run -p genegis-cli -- agent plan "名古屋市の人口密度を表示"
4647
cargo run -p genegis-cli -- agent execute
47-
- name: Agent audit export smoke (STAC bundle v2)
48+
- name: Agent audit export smoke (STAC bundle v3)
4849
run: |
4950
cargo run -p genegis-cli -- agent export-audit -o /tmp/genegis-audit-bundle.json
5051
python3 - <<'PY'
5152
import json
5253
from pathlib import Path
5354
bundle = json.loads(Path("/tmp/genegis-audit-bundle.json").read_text())
54-
assert bundle["schema"] == "genegis-audit-bundle-v2", bundle["schema"]
55+
assert bundle["schema"] == "genegis-audit-bundle-v3", bundle["schema"]
5556
stac = bundle["stac"]
56-
assert stac["collection"]["item_count"] == 5
57-
assert len(stac["items"]) == 5
57+
assert stac["alpha"]["collection"]["item_count"] == 5
58+
assert len(stac["alpha"]["items"]) == 5
59+
assert stac["overlay"]["record_count"] >= 1
60+
assert len(stac["merged"]["items"]) >= 6
5861
PY
5962
- name: STAC collection smoke
6063
run: cargo run -p genegis-cli -- catalog stac list

apps/desktop/ui/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<header class="topbar">
1111
<div class="brand">
1212
<strong>GeneGIS</strong>
13-
<span>AI-native GIS Workbench · Phase 9 beta</span>
13+
<span>AI-native GIS Workbench · Phase 9 gamma</span>
1414
</div>
1515
</header>
1616

crates/genegis-agent/src/audit.rs

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
33
use std::path::Path;
44

5-
use genegis_catalog::{alpha_catalog, bind_stac_item, browse_alpha_stac_collection};
5+
use genegis_catalog::{
6+
alpha_catalog, bind_stac_item, browse_alpha_stac_collection, extended_catalog,
7+
load_catalog_overlay, Catalog, DatasetRecord,
8+
};
69
use serde_json::Value;
710

811
use crate::error::AgentError;
912
use crate::model::AgentRun;
1013

11-
pub const AUDIT_BUNDLE_SCHEMA: &str = "genegis-audit-bundle-v2";
14+
pub const AUDIT_BUNDLE_SCHEMA: &str = "genegis-audit-bundle-v3";
1215

1316
/// Collab-side fields included in an audit bundle.
1417
#[derive(Debug, Clone)]
@@ -28,23 +31,42 @@ impl AuditCollabSnapshot {
2831
}
2932
}
3033

31-
/// Build STAC collection + item snapshot from the alpha catalog.
34+
fn stac_items_for_catalog(catalog: &Catalog) -> Result<Vec<Value>, AgentError> {
35+
catalog
36+
.list()
37+
.into_iter()
38+
.map(|record| {
39+
bind_stac_item(catalog, &record.id)
40+
.map_err(|err| AgentError::Message(err.to_string()))
41+
.and_then(|item| {
42+
serde_json::to_value(item).map_err(|err| AgentError::Json(err.to_string()))
43+
})
44+
})
45+
.collect()
46+
}
47+
48+
/// Build STAC alpha, overlay, and merged catalog snapshots for audit export.
3249
pub fn build_audit_stac_snapshot() -> Result<Value, AgentError> {
33-
let catalog = alpha_catalog();
34-
let collection = browse_alpha_stac_collection(&catalog);
35-
let mut items = Vec::new();
36-
37-
for record in catalog.list() {
38-
let item = bind_stac_item(&catalog, &record.id)
39-
.map_err(|err| AgentError::Message(err.to_string()))?;
40-
items.push(
41-
serde_json::to_value(item).map_err(|err| AgentError::Json(err.to_string()))?,
42-
);
43-
}
50+
let alpha = alpha_catalog();
51+
let merged = extended_catalog();
52+
let overlay = load_catalog_overlay();
53+
54+
let alpha_collection = browse_alpha_stac_collection(&alpha);
55+
let merged_collection = browse_alpha_stac_collection(&merged);
4456

4557
Ok(serde_json::json!({
46-
"collection": collection.summary_json(),
47-
"items": items,
58+
"alpha": {
59+
"collection": alpha_collection.summary_json(),
60+
"items": stac_items_for_catalog(&alpha)?,
61+
},
62+
"overlay": {
63+
"record_count": overlay.len(),
64+
"records": overlay.iter().map(DatasetRecord::summary_json).collect::<Vec<_>>(),
65+
},
66+
"merged": {
67+
"collection": merged_collection.summary_json(),
68+
"items": stac_items_for_catalog(&merged)?,
69+
},
4870
}))
4971
}
5072

@@ -105,14 +127,21 @@ mod tests {
105127

106128
let stac = bundle.get("stac").expect("stac");
107129
assert_eq!(
108-
stac.pointer("/collection/item_count").and_then(Value::as_u64),
130+
stac.pointer("/alpha/collection/item_count")
131+
.and_then(Value::as_u64),
109132
Some(5)
110133
);
111-
let items = stac
112-
.get("items")
134+
let alpha_items = stac
135+
.pointer("/alpha/items")
113136
.and_then(Value::as_array)
114-
.expect("stac items");
115-
assert_eq!(items.len(), 5);
137+
.expect("alpha items");
138+
assert_eq!(alpha_items.len(), 5);
139+
assert!(stac.get("overlay").is_some());
140+
assert_eq!(
141+
stac.pointer("/merged/collection/item_count")
142+
.and_then(Value::as_u64),
143+
Some(5)
144+
);
116145

117146
let runs = bundle
118147
.get("agent_runs")
@@ -131,7 +160,7 @@ mod tests {
131160
fn audit_stac_snapshot_includes_local_cog_item() {
132161
let stac = build_audit_stac_snapshot().expect("stac");
133162
let items = stac
134-
.get("items")
163+
.pointer("/alpha/items")
135164
.and_then(Value::as_array)
136165
.expect("items");
137166
assert!(

crates/genegis-ai/src/planner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ mod tests {
171171
let plan = plan_from_prompt("名古屋 GeoParquet 人口密度を表示").expect("plan");
172172
assert_eq!(plan.resolved.workflow_id, WorkflowId::NagoyaGeoparquetDensity);
173173
assert_eq!(plan.resolved.dataset_id, NAGOYA_WARDS_GEOPARQUET_ID);
174-
assert_eq!(plan.workflow.steps.len(), 7);
174+
assert_eq!(plan.workflow.steps.len(), 8);
175175
}
176176

177177
#[test]

crates/genegis-catalog/src/external_stac.rs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ use crate::stac::{StacCollection, StacItem};
1111
/// Default overlay path for imported STAC items.
1212
pub const CATALOG_OVERLAY_PATH: &str = ".genegis/catalog-overlay.json";
1313

14+
/// Environment variable override for overlay path (tests / CI isolation).
15+
pub const CATALOG_OVERLAY_ENV: &str = "GENEGIS_CATALOG_OVERLAY_PATH";
16+
17+
pub fn catalog_overlay_path() -> PathBuf {
18+
std::env::var(CATALOG_OVERLAY_ENV)
19+
.map(PathBuf::from)
20+
.unwrap_or_else(|_| PathBuf::from(CATALOG_OVERLAY_PATH))
21+
}
22+
1423
/// Fetch JSON bytes from an HTTP(S) URL or local filesystem path.
1524
pub fn fetch_json_bytes(uri: &str) -> Result<Vec<u8>, CatalogError> {
1625
let normalized = resolve_catalog_url(uri);
@@ -110,11 +119,11 @@ fn format_from_media_type(media_type: &str) -> DatasetFormat {
110119

111120
/// Load imported dataset records from the overlay file.
112121
pub fn load_catalog_overlay() -> Vec<DatasetRecord> {
113-
let path = Path::new(CATALOG_OVERLAY_PATH);
122+
let path = catalog_overlay_path();
114123
if !path.exists() {
115124
return Vec::new();
116125
}
117-
let json = match std::fs::read_to_string(path) {
126+
let json = match std::fs::read_to_string(&path) {
118127
Ok(json) => json,
119128
Err(_) => return Vec::new(),
120129
};
@@ -123,7 +132,7 @@ pub fn load_catalog_overlay() -> Vec<DatasetRecord> {
123132

124133
/// Persist imported dataset records to the overlay file.
125134
pub fn save_catalog_overlay(records: &[DatasetRecord]) -> Result<(), CatalogError> {
126-
let path = Path::new(CATALOG_OVERLAY_PATH);
135+
let path = catalog_overlay_path();
127136
if let Some(parent) = path.parent() {
128137
std::fs::create_dir_all(parent)
129138
.map_err(|err| CatalogError::Remote(format!("overlay dir: {err}")))?;
@@ -211,33 +220,27 @@ mod tests {
211220

212221
#[test]
213222
fn imports_sample_stac_item_to_overlay() {
214-
let overlay_path = Path::new(CATALOG_OVERLAY_PATH);
215-
let backup = overlay_path.exists().then(|| std::fs::read(overlay_path).expect("read"));
216-
let _guard = RestoreOverlay { backup };
223+
let overlay_path = std::env::temp_dir().join(format!(
224+
"genegis-overlay-test-{}.json",
225+
std::process::id()
226+
));
227+
std::env::set_var(CATALOG_OVERLAY_ENV, &overlay_path);
228+
let _guard = RestoreOverlayEnv;
217229

218230
let record = import_stac_item_url(&sample_item_path()).expect("import");
219231
assert_eq!(record.id, "genegis-sample-external-item");
220232
assert!(record.uri.ends_with("nagoya-wards.geojson"));
221233

222234
let overlay = load_catalog_overlay();
223235
assert!(overlay.iter().any(|entry| entry.id == record.id));
236+
let _ = std::fs::remove_file(&overlay_path);
224237
}
225238

226-
struct RestoreOverlay {
227-
backup: Option<Vec<u8>>,
228-
}
239+
struct RestoreOverlayEnv;
229240

230-
impl Drop for RestoreOverlay {
241+
impl Drop for RestoreOverlayEnv {
231242
fn drop(&mut self) {
232-
let path = Path::new(CATALOG_OVERLAY_PATH);
233-
match &self.backup {
234-
Some(bytes) => {
235-
let _ = std::fs::write(path, bytes);
236-
}
237-
None => {
238-
let _ = std::fs::remove_file(path);
239-
}
240-
}
243+
let _ = std::env::remove_var(CATALOG_OVERLAY_ENV);
241244
}
242245
}
243246
}

crates/genegis-catalog/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub use catalog::{
1414
};
1515
pub use external_stac::{
1616
fetch_stac_collection, fetch_stac_item, import_stac_item_url, load_catalog_overlay,
17-
resolve_catalog_url, CATALOG_OVERLAY_PATH,
17+
resolve_catalog_url, catalog_overlay_path, CATALOG_OVERLAY_ENV, CATALOG_OVERLAY_PATH,
1818
};
1919
pub use dataset::{DatasetFormat, DatasetRecord};
2020
pub use error::CatalogError;

crates/genegis-workflow/src/graph.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ pub fn nagoya_geoparquet_template() -> GeoWorkflow {
126126
serde_json::json!({ "tags": ["nagoya", "geoparquet", "demo"] }),
127127
),
128128
WorkflowStep::new("LoadGeoParquet", serde_json::json!({ "format": "geoparquet" })),
129+
WorkflowStep::new(
130+
"VerifyFeatureCount",
131+
serde_json::json!({ "expected": 16, "field": "ward_name" }),
132+
),
129133
WorkflowStep::new("AttachSources", serde_json::json!({})),
130134
];
131135
workflow

docs/guides/agent-orchestration.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,29 @@ Pending plans are stored at `.genegis/agent-plan.json`. Runs are stored at `.gen
4141
2. Sidebar → **Agent trace****Plan only** saves a pending plan.
4242
3. **Approve & execute** runs catalog → analysis → DuckDB verify without re-planning.
4343
4. Collab comments and project provenance record `agent_run_id` on failure or success.
44+
5. **External STAC** panel — fetch collection JSON or import STAC items into `.genegis/catalog-overlay.json`.
4445

4546
## Tool allowlist
4647

47-
Planner tools: `parse_intent`, `resolve_workflow`, `stac_browse`, `stac_bind`, `llm_plan_workflow`, `plan_workflow`.
48+
Planner tools: `parse_intent`, `resolve_workflow`, `stac_browse`, `stac_bind`, `stac_fetch`, `llm_plan_workflow`, `plan_workflow`.
4849

49-
Executor tools: `catalog_resolve`, `run_nagoya_density`, `run_remote_cog_metadata`, `run_local_cog_metadata`, `verify_retry`.
50+
Executor tools: `catalog_resolve`, `run_nagoya_density`, `run_remote_cog_metadata`, `run_local_cog_metadata`, `run_geoparquet_read`, `run_geoparquet_density`, `run_stac_fetch`, `verify_retry`.
5051

51-
Verifier tools: `duckdb_verify` (Nagoya density), `cog_metadata_verify` (remote COG metadata).
52+
Verifier tools: `duckdb_verify`, `cog_metadata_verify`, `geoparquet_feature_verify`, `stac_collection_verify`.
5253

5354
Unknown tools are rejected before execution (see `crates/genegis-agent/src/tool_registry.rs`).
5455

56+
## Verification matrix (Phase 9)
57+
58+
| Workflow | Execute tool | Verifier | Offline? |
59+
|----------|--------------|----------|----------|
60+
| `nagoya-density` | `run_nagoya_density` | `duckdb_verify` | Yes |
61+
| `remote-cog-demo` | `run_remote_cog_metadata` | `cog_metadata_verify` | Needs HTTP |
62+
| `local-cog-demo` | `run_local_cog_metadata` | `cog_metadata_verify` | Yes |
63+
| `nagoya-geoparquet` | `run_geoparquet_read` | `geoparquet_feature_verify` | Yes (fixture) |
64+
| `nagoya-geoparquet-density` | `run_geoparquet_density` | `duckdb_verify` | Yes (fixture) |
65+
| `external-stac-demo` | `run_stac_fetch` | `stac_collection_verify` | Yes (sample JSON) |
66+
5567
## Server API
5668

5769
```bash
@@ -61,7 +73,7 @@ curl http://127.0.0.1:7813/api/agent/runs/5bcfb044-7170-4aa1-b652-8f774d8cb28f
6173
curl -X POST http://127.0.0.1:7813/api/agent/runs -H 'Content-Type: application/json' -d @.genegis/agent-run.json
6274
```
6375

64-
Workbench proxies the same flow at `/api/agent/plan`, `/api/agent/execute`, `/api/agent/retry`, and `/api/agent/runs/latest`.
76+
Workbench proxies the same flow at `/api/agent/plan`, `/api/agent/execute`, `/api/agent/retry`, and `/api/agent/runs/latest`. STAC overlay APIs: `/api/stac/fetch`, `/api/stac/import`, `/api/stac/overlay`.
6577

6678
Tauri desktop uses the same UI with `invoke` commands (`agent_plan`, `agent_execute`, `agent_retry`, `agent_runs_list`, …).
6779

@@ -72,7 +84,7 @@ genegis agent export-audit -o .genegis/audit-bundle.json
7284
genegis collab provenance list
7385
```
7486

75-
Bundle includes collab summary, comments, provenance entries, agent run index, and STAC collection/items. Schema: `genegis-audit-bundle-v2` (see `crates/genegis-agent/src/audit.rs`).
87+
Bundle includes collab summary, comments, provenance entries, agent run index, and STAC snapshots (`alpha`, `overlay`, `merged`). Schema: `genegis-audit-bundle-v3` (see `crates/genegis-agent/src/audit.rs`).
7688

7789
## Provenance
7890

@@ -85,5 +97,6 @@ Successful or pending agent runs append to `Workspace.provenance` inside the col
8597
## References
8698

8799
- [`docs/roadmap/phase-6-autonomous.md`](../roadmap/phase-6-autonomous.md)
100+
- [`docs/roadmap/phase-9-external-data.md`](../roadmap/phase-9-external-data.md)
88101
- [`docs/adrs/0003-agent-trust-boundary.md`](../adrs/0003-agent-trust-boundary.md)
89102
- [`crates/genegis-agent/`](../../crates/genegis-agent/)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Phase 10: Federated Catalog Search (TBD)
2+
3+
**Goal:** Search and bind datasets across federated STAC endpoints and cloud GeoParquet assets.
4+
5+
**Status:** Draft placeholder — scope to be defined after Phase 9 gamma release.
6+
7+
## Candidate tracks
8+
9+
| Track | Focus |
10+
|-------|--------|
11+
| **Catalog** | Federated STAC search + merge into overlay |
12+
| **Storage** | Cloud GeoParquet range-read execution |
13+
| **Agent** | Multi-source workflow binding |
14+
| **Workbench** | Federated discovery UI |
15+
16+
## North star (unchanged)
17+
18+
「名古屋市の人口密度を表示」 — offline rule planner + DuckDB verification must keep passing in CI.
19+
20+
See [`phase-9-external-data.md`](phase-9-external-data.md).

docs/roadmap/phase-8-intent-expansion.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,6 @@ genegis workflow run remote-cog-demo --execute
8181

8282
## Next
8383

84-
Phase 9 — external STAC discovery and GeoParquet workflows (TBD): draft `docs/roadmap/phase-9-external-data.md`
84+
Phase 9 — external STAC discovery and GeoParquet workflows: see [`phase-9-external-data.md`](phase-9-external-data.md).
8585

8686
See [`phase-7-release.md`](phase-7-release.md) and [`docs/guides/agent-orchestration.md`](../guides/agent-orchestration.md).

0 commit comments

Comments
 (0)