Skip to content

feat(graphs): subgraph write-surface gates + durable-bytes storage accounting - #1079

Merged
jfrench9 merged 2 commits into
mainfrom
feature/subgraph-gates-storage-accuracy
Aug 7, 2026
Merged

feat(graphs): subgraph write-surface gates + durable-bytes storage accounting#1079
jfrench9 merged 2 commits into
mainfrom
feature/subgraph-gates-storage-accuracy

Conversation

@jfrench9

@jfrench9 jfrench9 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

Makes the storage breakdown account for everything actually on disk — blue-green build artifacts and leftovers of deleted subgraphs get their own item types instead of masquerading as (or hiding from) the subgraph footprint — and enforces the storage cap on durable bytes only, so an in-flight rebuild can't block the very operation that reclaims it. Separately, closes the surfaces a subgraph must not have: the staging pipeline, materialization, and semantic-memory writes, on both REST and the MCP connector.

Changes

Storage accounting (graph_api/core/storage_breakdown.py, middleware/graph/ingestion_limits.py)

  • New transient item type for blue-green -wip/-prev artifacts. Previously a subgraph's build artifact was classified as a subgraph, while the parent's own {id}-wip (hyphenated) escaped the ownership test and went uncounted entirely.
  • New orphan item type, applied by the platform layer: {parent}_* items with no live row in the graph registry. Covers a deleted subgraph's whole estate — database, vector index, and staging file — with the {parent}_memory index explicitly protected from mislabelling. This is what made /usage (sums by type) and the subgraphs page (sums by registered id) agree again.
  • Cap enforcement moves to a new enforced_storage_gb figure that excludes transient bytes: a rebuild briefly holds a copy the size of the database, and a crashed build's leftover would otherwise block the materialize that rebuilds and reclaims it. Orphans stay counted — they're durable, and blocking on them surfaces registry drift instead of hiding it. total_storage_gb remains the full real-disk figure (metering truth; the itemized list sums to it).
  • The ingest-file headroom check and tier-downgrade validation read the enforced figure; /limits' approaching_limit flag uses the same durable-bytes basis. Registry lookups ignore soft-deleted rows.
  • Precision: total_storage_gb and current_usage_gb report at byte level instead of 2-decimal GB (a 0.01 GB quantum erased small tenants' footprints and contradicted the itemized bytes beneath them); size_mb likewise.

Subgraph surface gating (routers/graphs/operations.py, routers/graphs/content_ops.py, middleware/mcp/tools/manager.py)

  • Subgraphs are the direct-write surface (raw Cypher, schema extension); the staging pipeline rebuilds the database and file-swaps it into place, discarding direct writes. The two write models can't share one database, so materialize, create-file-upload, and ingest-file now 403 on subgraphs.
  • Semantic memory is parent-level: remember and update-memory are refused on subgraphs. The exits stay open deliberately — forget and delete-file still work so anything stored before the gate remains removable.
  • The MCP connector mirrors the same gates (materialize, get-graph-sync-status, remember, update-memory withheld), since MCP dispatch bypasses FastAPI dependencies. sync-connection is withheld alongside its read half: connections belong to the parent, and a sync feeds the staging pipeline the subgraph doesn't have.

Reviewer notes

  • The enforcement-basis change is the deliberate policy decision in this PR: transient bytes are visible (itemized, in totals) but not enforced. See the comment block in check_instance_storage.
  • There is currently no reclaim path for a stale -wip left by a crashed build other than the next materialize; a GC follow-up may be worth filing.

Breaking Changes

None. StorageItem.type is a plain string in the schema, so the new transient/orphan values are shape-compatible; enforced_storage_gb is internal and not exposed in the response models. Description-text updates can ride the next SDK regen — no coordinated release needed.

Testing

Full gate run this session: just test-all — 12,249 passed, 0 failed (plus dbt, ruff, format, basedpyright, cf-lint all clean). New coverage includes: transient classification and the parent's own artifacts counting; orphan labelling across the estate (and the memory-index exclusion); enforcement excluding transient while keeping orphans and genuine overages blocking; registry-failure fail-open for labelling; REST and MCP subgraph gates firing before any command runs, with the parent unaffected.

jfrench9 added a commit to RoboFinSystems/robosystems-python-client that referenced this pull request Aug 7, 2026
…es (#173)

## Summary

Regeneration picking up two API changes: the byte-precision subgraph
size fields already merged on the API's `main`, and the storage-item
documentation from RoboFinSystems/robosystems#1079 (`transient`/`orphan`
item types, enforcement semantics for `usage_percentage`). Generated
against the local stack at #1079's tip; if that PR changes in review,
this regen will be refreshed.

## Changes

- **Regenerated `models/` only** — no `api/` or facade changes; sync and
async paths unaffected.
- `SubgraphResponse`, `SubgraphSummary`: new optional `size_bytes`;
`ListSubgraphsResponse`: new optional `total_size_bytes`. `size_mb`
remains, documented as derived.
- `StorageItem`, `InstanceUsage`: docstring updates only — the new
`transient` (blue-green build artifact) and `orphan` (unregistered
leftover) item type values, and a note that `usage_percentage` derives
from durable bytes. `type` was and remains a plain `str`, so the new
values are shape-compatible.

## Compatibility

ADDITIVE. All new fields are optional (`| None | Unset = UNSET`); no
removals, renames, signature changes, or requiredness flips — verified
by reading the full emitted diff, not assumed from the regeneration.
Rides the next minor.

## Testing

`just test-all` run this session: 519 passed, 17 skipped; ruff format,
lint, and basedpyright clean. No packaging changes, so `just
build-package` was not needed.
jfrench9 added a commit to RoboFinSystems/robosystems-typescript-client that referenced this pull request Aug 7, 2026
…180)

## Summary

Regeneration picking up two API changes: the byte-precision subgraph
size fields already merged on the API's `main`, and the storage-item
documentation from RoboFinSystems/robosystems#1079 (`transient`/`orphan`
item types, enforcement semantics for `usage_percentage`). Generated
against the local stack at #1079's tip; if that PR changes in review,
this regen will be refreshed.

## Changes

- **Regenerated `sdk/types.gen.ts` only** — no hand-written extensions
or tooling touched.
- `SubgraphResponse`, `SubgraphSummary`: new optional `size_bytes`;
`ListSubgraphsResponse`: new optional `total_size_bytes`. `size_mb`
remains, documented as derived.
- `StorageItem`, `InstanceUsage`: doc-comment updates only — the new
`transient` (blue-green build artifact) and `orphan` (unregistered
leftover) item type values, and a note that `usage_percentage` derives
from durable bytes. `type` was and remains `string`, so the new values
are shape-compatible.

## Compatibility

ADDITIVE. All new fields are optional (`?: number | null`); no removals,
renames, or narrowed types — verified by reading the full emitted diff,
not assumed from the regeneration. Rides the next minor.

## Testing

`npm run test:all` run this session: validate (prettier, eslint, tsc),
295 tests across 10 files passed, and the build completed.
@jfrench9
jfrench9 merged commit 27678da into main Aug 7, 2026
7 checks passed
@jfrench9
jfrench9 deleted the feature/subgraph-gates-storage-accuracy branch August 7, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant