Skip to content

feat(security): land Engram 10/10 wave 4 foundations - #192

Open
limaronaldo wants to merge 18 commits into
mainfrom
feat/engram-10-of-10-wave4
Open

feat(security): land Engram 10/10 wave 4 foundations#192
limaronaldo wants to merge 18 commits into
mainfrom
feat/engram-10-of-10-wave4

Conversation

@limaronaldo

@limaronaldo limaronaldo commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lands Engram 10/10 Wave 4 on main: SDK live package contracts, retrieval/performance budget gates, reproducible release attestation, gated SDK publication workflows, and realtime/HTTP resource bounds.

Todos integrated

  • 22 — Python SDK installed-wheel live contract
  • 23 — TypeScript SDK packed-package live contract
  • 24 — Retrieval + Criterion quality budgets in required CI
  • 26 — Reproducible release artifacts, SBOM, signatures, provenance
  • 27 — Dry-run-safe Python/npm publication lanes
  • 37 — WebSocket workspace filtering and Origin policy
  • 38 — WebSocket connection/message/idle resource bounds
  • 39 — HTTP max body size and request lifetime bounds

Todo 24 human escalation

Two consecutive independent-review FAILs were remediated in 6a42f00. Human owner responded autorizado with scope limited to accepting/integrating that remediated SHA. Not publication approval for any channel.

Release dry-run evidence

  • First dry-run 29271674475 failed closed (unsupported engram-server --version smoke).
  • Remediation 2cfab45 aligned smoke with shipped CLIs.
  • Superseding dry-run 29286930522 success on exact SHA 2cfab4563d5da43932c1cc3aa6741eeea6b487ea.
  • GitHub Release and Homebrew jobs skipped. No tag, registry, Homebrew, or deploy write.

Closeout on this PR

  • Advertised-surface inventory includes Todo 39 HTTP env vars.
  • PDF worker packaging checker aligned with Wave 4 release workflow (make ci green).
  • Independent review: docs/harness/reviews/2026-07-13-engram-10-of-10-wave4-post.mdREVIEW_VERDICT: PASS
  • Full sensors: 2026-07-13T23:20:58Z pass.

Explicit non-claims

  • Atomic descriptor-bound SQLite opening remains deferred.
  • No v0.22.0 tag, GitHub Release, crates.io, PyPI, npm, Homebrew, or deployment is published by this PR.
  • Plan approval, dry-run success, PR checks, and Todo 24 acceptance are not channel publication approval (Todos 36 / 52–55).

Test plan

  • Focused Wave 4 acceptance surfaces
  • make ci pass (no exclusions)
  • Full harness sensors pass
  • Live-state checker pass
  • Independent REVIEW_VERDICT: PASS
  • CI required checks green on this PR
  • Human merge authorization (do not merge without explicit ask)

Record integration HEAD 2cfab45, Todo 24 human acceptance scope,
successful release dry-run 29286930522 with publication jobs skipped,
SQLite deferral, and the Todo 39 HTTP body/timeout env vars in the
advertised-surface inventory.
Update the PDF worker packaging checker and unit suite for the Wave 4
release.yml shape: locked multi-binary build, deterministic artifact
scripts with Linux-only worker packaging, and Homebrew formula updater.
Keep release dry-run 29286930522 bound to 2cfab45, record the PDF packaging
contract alignment, and advance live-state Last commit with integration HEAD.
@huly-for-github

Copy link
Copy Markdown

Connected to Huly®: ENGRA-228

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Engram Performance

Details
Benchmark suite Current: 667ab03 Previous: 962655a Ratio
entity_extractor_new/default 3538 ns/iter (± 95) 2322 ns/iter (± 7) 1.52
entity_extraction/extract_mixed 379866 ns/iter (± 8869) 249155 ns/iter (± 482) 1.52

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Engram Performance'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.15.

Benchmark suite Current: 667ab03 Previous: 962655a Ratio
entity_extractor_new/default 3538 ns/iter (± 95) 2322 ns/iter (± 7) 1.52
entity_extraction/extract_mixed 379866 ns/iter (± 8869) 249155 ns/iter (± 482) 1.52

This comment was automatically generated by workflow using github-action-benchmark.

@limaronaldo limaronaldo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainability review — changes requested (posted as COMMENT: GitHub forbids REQUEST_CHANGES on self-authored PRs)

Author action still required before merge. Treat this as a formal request-changes review.

Behavior/security direction is largely right (WS isolation, HTTP timeout/auth ordering, dry-run publish guards, quality budgets). This review is not a functional rubber-stamp; it is a structural quality bar.

P0 blockers

  1. src/realtime/server.rs 713 → 1325 — crosses the 1k-line threshold hard. Origin policy, metrics/guards, socket lifecycle, and ~636 lines of tests all landed in one module.
  2. sdks/python/engram_client/client.py 996 → 1055 — MCP decode path pushes the client god-file over 1k for a transport-boundary fix that should live in a small helper module.
  3. .with_workspace(...) scatter — isolation is a model invariant, but enforcement is “remember to chain a builder at every emit site.” One missed call is a silent cross-workspace leak.

P1

  1. HTTP resource env parse soft-defaults; WS resource env parse fail-closes. Pick one policy (prefer fail-closed / shared helper).
  2. enforce_mcp_auth / enforce_sse_auth are copy-paste twins that will drift.
  3. Release/live verify scripts and SDK live drivers multiply near-clones without a shared lib — high leverage follow-up after P0.

Minimum before approve

  • Decompose server.rs (origin / metrics / socket / tests out).
  • Make workspace required on memory realtime events or a single broadcast_memory_* API.
  • Extract MCP decode from client.py under 1k lines.
  • Align HTTP env parse with WS fail-closed (or one shared helper).

I will re-review after the judo split. I will not approve “it works” growth of the realtime god-file.

Comment thread src/realtime/server.rs Outdated
/// Aggregate resource counters. They intentionally contain no principal,
/// credential, workspace, or message data.
#[derive(Default)]
struct RealtimeMetrics {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 — file size / god-module growth. server.rs went 713 → 1325 lines. Metrics + drop guards belong in something like src/realtime/metrics.rs, not more surface on the already-busy transport module.

This is a presumptive blocker: please decompose before merge so product code in server.rs is clearly under ~1k (ideally ~400–500), with tests extracted.

Comment thread src/realtime/server.rs Outdated
allowed_origins.contains(origin)
}

fn parse_origin_allowlist(raw: Option<&str>) -> Result<HashSet<String>, String> {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code-judo: extract origin policy. parse_origin_allowlist + origin_is_allowed are a self-contained policy surface (scheme/authority, no wildcards, multi-Origin reject). They do not need to live next to socket select loops.

Suggested split:

src/realtime/origin.rs   # parse + allow checks
src/realtime/auth.rs     # principal_can_subscribe / websocket_principal
src/realtime/socket.rs   # handle_socket + write/close timeouts
src/realtime/server.rs   # router + RealtimeServer only

config.rs already shows the right pattern — follow it.

Comment thread src/realtime/server.rs Outdated

/// Handle an individual WebSocket connection
async fn handle_socket(socket: WebSocket, manager: RealtimeManager, principal: TransportPrincipal) {
async fn handle_socket(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter explosion / lifecycle ownership. handle_socket now takes manager + principal + workspace + config + metrics + permit. That is a signal the connection lifecycle wants its own type (ConnectionSession { permit, metrics_guard, registration_guard, ... }) in a dedicated module rather than a longer free function in the god file.

Keep the Drop-guard idea — it is the right ownership model — but move it out of server.rs.

Comment thread src/realtime/server.rs Outdated
}

#[cfg(test)]
mod tests {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~636 lines of mod tests in-tree roughly double the product surface of this file. Move to src/realtime/server_tests.rs (or integration tests under tests/) as part of the decomposition that brings this file back under 1k.

Leaving the suite here is how the 1k threshold was crossed even if the product logic alone might still fit.

Comment thread src/realtime/events.rs Outdated
}

/// Attach the authoritative workspace used by realtime transports.
pub fn with_workspace(mut self, workspace: impl Into<String>) -> Self {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 — wrong abstraction for a security invariant. Optional .with_workspace(...) means isolation is a call-site discipline, not a type invariant. One missed chain on a future emit site = silent cross-workspace delivery.

Code-judo (prefer one):

  1. Require workspace on constructors: memory_created(id, preview, workspace).
  2. Or RealtimeManager::broadcast_memory_created(&Memory) that always copies memory.workspace.
  3. Or make workspace non-optional on every memory event variant.

Please delete the “remember to call the builder” class of bugs.

Comment thread src/mcp/handlers/memory_crud/create.rs Outdated
));
manager.broadcast(
RealtimeEvent::memory_created(memory.id, memory.content.clone())
.with_workspace(memory.workspace.clone()),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaghetti growth at emit sites. Same .with_workspace(memory.workspace.clone()) pattern is copy-pasted across create / facts / update / delete. That is special-case branching bolted onto every handler instead of pushing the invariant into the event/broadcast API.

If the event constructor requires workspace (or a single broadcast helper owns it), these chains disappear entirely — including the delete path’s (ids, workspace) plumbing.

parse_positive_value(name, &value, default, zero)
}

fn parse_positive_value<T>(name: &str, value: &str, default: T, zero: T) -> T

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 — inconsistent fail modes. Invalid/zero ENGRAM_HTTP_MAX_BODY_BYTES / ENGRAM_HTTP_REQUEST_TIMEOUT_MS warn and soft-default. WS resource env parse in RealtimeConfig::from_env fail-closes and refuses to listen.

Two security-resource models in one product is a maintainability and ops hazard. Prefer one shared parse policy — ideally fail-closed like WS — rather than silent defaults that paper over misconfig.

Comment thread src/mcp/http_transport/router.rs Outdated
.with_state(state)
}

async fn enforce_mcp_auth(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate auth middleware. enforce_mcp_auth and enforce_sse_auth are the same principal check with different unauthorized response shapes. They will drift.

Collapse to one middleware parameterized by failure response (or a tiny helper that returns the unauthorized Response), and keep the timeout middleware separate. Layering order itself is good — do not lose that.

Comment thread sdks/python/engram_client/client.py Outdated
return self._decode_tool_result(result.get("result", {}))

@staticmethod
def _decode_tool_result(result: Any) -> Any:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 — file crossed 1k (996 → 1055). MCP CallToolResult decoding + error unwrapping is real product logic and does not need to live on the god-client.

Extract to e.g. sdks/python/engram_client/mcp_result.py (decode_tool_result, error_message) and keep EngramClient._call thin. The decode behavior is fine; the layering is not.

Decompose realtime transport into origin/metrics/auth/socket modules and
extract tests so server.rs is under 500 lines. Require workspace on memory
RealtimeEvent constructors to remove the .with_workspace footgun. Extract
Python MCP decode/post helpers and EngramError, bring client.py under 1k.
Make HTTP body/timeout env parsing fail closed like WebSocket limits, and
collapse MCP/SSE auth middleware onto one shared principal gate.
@limaronaldo

Copy link
Copy Markdown
Collaborator Author

Maintainability P0 remediation (follow-up to review)

Addressed the structural review on 667ab03:

Finding Resolution
server.rs 713→1325 Split into origin / metrics / auth / socket + server_tests.rs. server.rs ≈ 442 LOC
Optional .with_workspace Removed. memory_created/updated/deleted require workspace
client.py over 1k Extracted errors.py + mcp_result.py. client.py ≈ 963 LOC
HTTP soft-default limits Body/timeout env parse is fail-closed (aligned with WS)
Duplicate auth middleware Shared enforce_auth gate for MCP/SSE

Local verification: realtime unit suite, HTTP security suite, security_config fail-closed tests, pre-commit clippy.

CI note

Required gates on this SHA were green (Test ubuntu, Security Gate, Clippy, Format, SDK live contracts, etc.).
Benchmark Regression Check reported ~1.53× on both Criterion suites vs main — same ratio on both benches looks like runner noise / baseline comparison, not a regression from this refactor (entity extractors untouched). Quality-budget gate in the required Test job still uses the frozen Criterion artifact and passed. Re-ran failed jobs to re-measure.

Happy to re-review / merge once CI is fully green (or if Benchmark remains noisy, we can treat it as non-blocking for this PR and keep the deterministic check-quality-budgets as the required floor).

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