Skip to content

[AAASM-5612] 🐛 (docs): Remove unverified SaaS operational and contractual claims - #134

Merged
Chisanan232 merged 38 commits into
mainfrom
v0.0.1-rc.7/AAASM-5612/remove_unverified_saas_claims
Aug 6, 2026
Merged

[AAASM-5612] 🐛 (docs): Remove unverified SaaS operational and contractual claims#134
Chisanan232 merged 38 commits into
mainfrom
v0.0.1-rc.7/AAASM-5612/remove_unverified_saas_claims

Conversation

@Chisanan232

@Chisanan232 Chisanan232 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Description

Removes unverified managed-service (SaaS) operational and contractual claims from the three pages this ticket owns — quickstart-saas.md, cloud-deployment.md, open-core-boundary.md — and adds a publication checklist that gates their return.

The pages were labelled Coming soon but still described regions, purchasable plans and quotas, onboarding and console steps, support-response times, billing and procurement, SSO/SCIM configuration, an availability SLA with service credits, data residency, and DPA/BAA and compliance posture as though the managed service were operating and contractually defined. Coming soon above a page that then tells a reader to enter card details, select a region, and expect a 4-hour support response does not neutralise those statements — a reader takes the commitment away regardless of the banner.

Claims were removed, not softened. Rewording "99.9% uptime SLA" into "high availability" does not reduce what a reader takes away; it only makes the claim harder to audit. Where a capability is genuinely intended, it is stated as a capability area under an explicit 🗺️ Planned — not available label, with no plan, price, quota, retention period, region, availability or compliance detail attached.

Type of Change

  • 📝 New documentation page
  • 🐛 Bug fix (correcting inaccurate or missing content)
  • ♻️ Refactor / restructure (no content change)
  • 🔧 Configuration / CI change
  • ✨ New section or feature documentation (the publication checklist page)

Related Issues

  • Related Jira ticket: AAASM-5612
  • Epic: AAASM-4224 — SaaS documentation
  • Goal: CBLPCRLM-13 — Verified Product Truth and Protection Boundaries
  • Fix version: agent-assembly v0.0.1-rc.7
  • Related GitHub issues: none

Documentation Checklist

  • cd docs && mdbook build passes locally — no errors. Two pre-existing preprocessor version-skew warnings are emitted (mdbook-mermaid and gettext built against different mdBook patch versions); they are present on origin/main too and are non-fatal
  • Internal cross-links verified — lychee resolves every target, and the "Related documentation" descriptors were re-read on all five pages, since lychee cannot detect a descriptor advertising content its target no longer has (this exact defect was caught in review on security-model.md and fixed in 8f35626)
  • Page registered in docs/src/SUMMARY.md
  • Self-hosted instructions not included (SaaS-only scope) — no self-host instructions are authored here; the pages link to the existing docker-containers.md / self-host-observability.md pages
  • "Last reviewed" footer updated with today's date
  • Commits follow GitEmoji convention and are small / atomic

Problem

Three public pages presented an unavailable managed service as an operating, contractually defined one. This is a P0 truthfulness risk and a commercial/legal exposure: prices, availability percentages, support-response times, data-residency guarantees and the availability of a DPA or BAA are representations a prospect can rely on.

Concretely, the pages contained:

  • A signup URL, a contact-sales URL and a community-forum URL presented as reachable.
  • An SDK walkthrough instructing readers to copy a workspace ID and API key out of console screens, and to point the SDK at a gateway endpoint.
  • Three purchasable tiers with agent/policy quotas, retention periods, onboarding-duration estimates and support-response times.
  • A three-region table with a "data at rest and in transit stays within the selected region" guarantee.
  • A week-by-week procurement timeline, DPA/BAA handling and a named compliance set (SOC 2, HIPAA, GDPR).
  • An SLA table with availability percentages and service credits, and card/invoice billing setup naming a payment processor and payment terms.
  • A feature matrix asserting shared-region SaaS as already available on the open-source side.
  • A named commercial licence whose terms are published nowhere, and a footer attributing legal sign-off to content that has since changed.

Outcome

  • quickstart-saas.md and cloud-deployment.md now state what actually ships (with links to the pages that document it), enumerate explicitly what this hub will not publish and why, and link the publication checklist.
  • open-core-boundary.md keeps its open-core rationale — that argument is sound and unchanged — but its tier matrix is replaced by two sections: what is in the Apache-2.0 core today, and what is intended for the commercial tier, the latter under a Planned label with no operational or commercial detail.
  • New docs/src/saas-claim-publication-checklist.md records, per claim class: the claim, why it was removed, the role that must approve restoring it, and the concrete evidence that approval requires. It also carries the language rules (no present tense for unshipped capability, label the maturity, no unqualified absolutes, no unmeasured numbers, remove rather than soften).

Scope

File Change
docs/src/quickstart-saas.md Rewritten — managed-onboarding walkthrough and tier sections removed
docs/src/cloud-deployment.md Rewritten — regions, tenants, quotas, SSO/SCIM, RBAC, budget form, SLA, billing, DPA/BAA removed
docs/src/open-core-boundary.md Targeted edits — framing downgraded, tier matrix replaced, licence/legal claims removed
docs/src/security-model.md Targeted edits — compliance table removed; audit-log integrity, retention and export claims bounded to verified behaviour; CTA corrected
docs/src/self-host-observability.md Two lines — scope callout and two related-doc descriptors; removes the last surviving 99.5%/99.9% SLA figures in the hub
docs/src/glossary.md Five term definitions — AES-256-GCM/vault, Ed25519, HMAC-SHA256, Audit log, SSO/SAML/OIDC and SCIM — brought into line with security-model.md
docs/src/saas-claim-publication-checklist.md New page
docs/src/SUMMARY.md One line — registers the new page under About

Out of scope

Deliberately not touched, and reported to the ticket instead:

  • docs/src/comparison.md and docs/src/README.md — owned by AAASM-5528 in this same wave.
  • docs/src/faq.md, docs/src/docker-containers.md — outside this ticket's path ownership. Residual claims found there are listed under Claim impact below.

Scope extended mid-review. docs/src/security-model.md was initially reported as out of scope and is now in scope: this ticket's own acceptance criterion covers compliance/legal language, and that page carried the highest-risk residual claim in the hub. Fixed here.

  • Re-authoring the removed material as labelled design previews — that is AAASM-5613, which follows this PR.
  • docs/po/zh-Hant.po — the translation catalog still holds msgstr entries for the removed English strings. They are unreachable (gettext only emits a translation when its msgid is still present in the source, and the zh-Hant build was verified clean), so they are stale catalog entries, not published claims. Left for a msgmerge pass rather than hand-edited here.

Acceptance criteria mapping

AC Where it is met Evidence
No public page presents unavailable SaaS functions or commitments in the present tense All three pages open with a 🗺️ Planned — not available banner; every remaining managed-service statement is phrased as intent Built-HTML scan below returns 0 hits on the owned pages
Coming soon pages do not contain fake-operational instructions or contractual promises Console navigation, credential-issuance steps, signup/contact-sales URLs, billing setup, SLA table and procurement timeline all deleted git diff on the two SaaS pages
Compliance/legal language is reviewed and either evidenced or removed SOC 2 / HIPAA / GDPR / DPA / BAA references, the unpublished commercial licence name, and the stale legal-approver footer removed; a no-licence-grant note added; the security model's compliance status table removed after searching for and failing to find any evidence artifact Commits 7a87b82, 6f98534
Planned content is clearly separated from current OSS/self-host capabilities Each page carries a distinct "what ships today" section (linking core docs, the SDK docs, policy-reference, docker-containers, self-host-observability) separated from a Planned section open-core-boundary.md §"What is in the Apache-2.0 core today" vs §"What is intended for the commercial tier"
Docs build, links, metadata and claim scans pass See Tests and CI expectations All gates green locally

Dependency mapping

  • Relates to AAASM-5613 — converts this material into labelled design previews. It follows this PR: 5612 removes, 5613 re-authors what survives review as an explicit Design Preview. Merging 5613 first would re-publish claims this PR is removing.
  • Adjacent to AAASM-5528 — owns comparison.md and README.md in the same wave. No file overlap with this PR, so the two can merge in either order; the residual claims listed below are handed to it.
  • Relates to AAASM-5626.
  • Feeds AAASM-5621 (canonical claim-governance ADR) and AAASM-5531 (capability/evidence manifest). The publication checklist added here is explicitly marked provisional pending 5621 and is expected to become an adoption record for 5531 rather than a standalone registry — see the banner at the top of the page.
  • ⚠️ MERGE ORDER IS A HARD CONDITION: AAASM-5528's PR [AAASM-5528] 📝 (docs): Bound absolute enforcement claims to verified paths #135 must merge before or with this PR. This PR makes security-model.md and glossary.md state that there is no HMAC over audit records. comparison.md — owned by 5528 — and the concatenated print.html still say there is. Until [AAASM-5528] 📝 (docs): Bound absolute enforcement claims to verified paths #135 lands, the hub self-contradicts on a disproved cryptographic claim, which is a worse published state than either PR alone.
  • No overlap with open PR [AAASM-5017] 📝 (docs): add light/dark dashboard screenshots to observe docs #131 (v0.0.1/AAASM-5017/docs/dashboard_screenshots) — disjoint file sets.

Architecture implications

None to the product. One documentation-architecture change: docs/src/saas-claim-publication-checklist.md becomes a standing gate that future managed-service claims must pass through, sitting alongside source-of-truth.md (which owns the maturity-label vocabulary this PR reuses rather than inventing a parallel one). It is registered under About, with the pages it governs linking to it directly.

Product-truth implications

This PR reduces the published surface substantially, and that is the point — the hub previously claimed more than the product does. The specific truth changes:

  • The hub no longer publishes any availability, support-response, pricing, quota, region, data-residency, or compliance commitment for the managed service.
  • The open-source capability list is now stated by pointing at the page that documents each item, so it cannot silently drift ahead of what those pages support.
  • Two unqualified absolutes were removed ("every security control in the stack", "all interception and enforcement logic") and one rephrased ("the complete feature set stays SaaS"). Retained absolutes name their boundary: "All Cargo crates in the agent-assembly workspace are Apache-2.0" is bounded by the enumerated table directly beneath it.
  • "Tamper-evident" is no longer used as a settled capability name anywhere on these pages; the checklist records that restoring it requires a named integrity mechanism and a reader-runnable verification procedure.

Security considerations

  • No code, CI, dependency, permission or workflow change; documentation only.
  • Removing an unevidenced "tamper-evident audit" claim is a security-posture correction: an integrity guarantee a reader cannot verify is worse than no claim, because it discourages the compensating control they would otherwise apply.
  • The pages continue to describe the open-source enforcement path, which is unchanged.
  • No private-repo content, internal implementation detail, or non-public business context appears in the diff. The checklist's "evidence required" entries are forward-looking requirements, deliberately written so they disclose nothing about current internal state.

Tests

This repo has no test suite; the gates are the build and the three metadata checks. All run from the repo root unless noted.

Command Result
cd docs && mdbook build Pass — HTML written, no errors (two preprocessor version-skew warnings are pre-existing and non-fatal)
cd docs && MDBOOK_BOOK__LANGUAGE=zh-Hant mdbook build -d book/zh-Hant Pass
python3 docs/scripts/generate_hub_components.py --check Pass (exit 0)
python3 docs/scripts/check_repo_names.py --check Pass (exit 0)
python3 docs/scripts/generate_compatibility.py --check Pass (exit 0)
lychee over the seven changed pages 108 total, 34 unique, 108 OK, 0 errors. A whole-docs/src run reports 11 errors, all pre-existing and in files this PR does not touch: root-relative /core/-style links that need --root-dir (they resolve on the deployed site) and an npmjs 403 bot-block
Pagefind (npx pagefind@1.4.0 --site book, the same pin aggregate.sh uses) 44 pages, 2 languages, 6,302 words indexed (en + zh-Hant), exit 0 — index scanned per-page, see below
Anchor targets verified in built HTML #budget (policy-reference), #compose (docker-containers), #crate-licensing and #what-is-in-the-apache-20-core-today (open-core-boundary) all present
Every commit builds (bisectability) Each of the 38 commits checked out into a detached worktree and built — 38/38 BUILD OK
markdownlint-cli2 --config .markdownlint.json Informational only — not a CI gate in this repo. Remaining MD060/MD036/MD034 findings match the pre-existing baseline on untouched pages (policy-reference.md, security-model.md)

Claim-survival scan — re-grepped the built output, not the source, for the removed vocabulary (99.9%, net-30, Stripe, purchase order, Sales Engineer, contact-sales, app.agent-assembly.com, community.agent-assembly.com, us-east-1/eu-west-1/ap-northeast-1, ws-<uuid4>, HIPAA, SOC 2, ISO 27001, 24/7, tier names, dedicated SRE, SLA credit, credit card, Tenant ID, BudgetExceededError, Generate SCIM Token, Enforce SSO, latency=2ms, duration estimates):

  • book/quickstart-saas.html and book/open-core-boundary.html0 hits
  • book/cloud-deployment.html — 1 hit, the "an earlier version of this page described…" transparency paragraph
  • book/security-model.html — 5 hits, every one inside an explicit negation ("There is no log-signing key, no HMAC…", "There is no CEF output…", "No SOC 2, ISO 27001, or equivalent audit has been performed…", "No Data Processing Agreement or Business Associate Agreement is available")
  • All four pages under book/zh-Hant/ — identical results
  • One apparent hit on every page was a substring artifact: CEF matching inside "gracefully" in the shared theme script. Re-run with \bCEF\b to reproduce cleanly
  • book/saas-claim-publication-checklist.html — 7 hits, all deliberate: the register names each removed claim class in an explicitly negating context
  • book/searchindex-*.js and book/zh-Hant/searchindex-*.js — the only hits trace to the checklist register and to two pages outside this ticket's ownership (security-model.md, self-host-observability.md), listed under Claim impact

Search-snippet check (Pagefind). Previously skipped; now run. The 44 index
fragments were decompressed and scanned per page, attributing every hit to its
source URL:

Page Tokens in index Assessment
quickstart-saas, open-core-boundary (both languages) none clean
cloud-deployment saml, oidc inside the "this hub does not publish SSO/SCIM instructions" negation list
security-model saml, oidc, cef, iso 27001, log-signing, aes-256-gcm, hsm every one inside an explicit negation ("Operator SSO … is not implemented", "There is no CEF output", "There is no log-signing key", "No AES-256-GCM implementation exists")
saas-claim-publication-checklist the full removed-claim vocabulary by design — the register names each removed claim class
glossary aes-256-gcm, hsm, log-signing, saml, oidc, scim every one inside a retraction or a Planned label — the AES/HSM terms are deliberately retained carrying their own correction
all thirty retired phrases across every round (incl. Connection pool limits, circuit breaker on upstream, writes its own JSONL audit record, and agent registration unauthenticated, OpenAPI schema validation rejects, SDK integrity verified by, runs as unprivileged user, per-session JSON) zero hits, both languages the run also positively confirms 8 replacement markers indexed on security-model (nothing bounds connection concurrency, no proxy audit file is produced, cannot be activated from a released build, absent unless a policy declares, one fixed file, not per-session, …). Every surviving per-session occurrence was inspected in context and is a negation
comparison tamper-evident signatures, saml, oidc, scim not this PR's file — AAASM-5528 owns it, and has this evidence
print.html union of all of the above mdBook's concatenated print page; inherits from comparison/glossary, not an independent source

The check earned its keep: it confirmed the 99.5% / 99.9% figures now appear
only in the checklist register — i.e. the self-host-observability.md regression
(R2) is genuinely gone from the search index, not just from the page body — and it
surfaced the aes-256-gcm glossary entry, which was not in my earlier residuals list and became the final commits of this PR. A third run after the glossary fix confirmed scim also appears on README, docker-containers, faq and source-of-truth; only source-of-truth labels it correctly, and the other three are already in the residuals below.

CI expectations

Both workflows trigger on pull_request to main with no path filter, so both run:

  • Aggregate and Deploy Docs Hub (aggregate.yml) — clones every module, builds each generator, builds the hub at root plus zh-Hant, runs Pagefind, and fails if any module's output is missing or empty. The hub-side portion is what this PR affects and it builds clean locally on the same pinned mdBook 0.5.2 / mdbook-mermaid 0.17.0 the workflow installs. The deploy job is gated on refs/heads/main and will not run for this PR.
  • Hub metadata drift check (hub-metadata-check.yml) — all three --check scripts pass locally.

Known environment risk: org GitHub Actions have been billing-blocked before, aborting jobs in seconds with a payments annotation. If that happens, check the run annotations before triaging it as a real failure — every gate was validated locally.

Sonar / coverage expectations

Not applicable. This repo has no SonarCloud project and no coverage reporting — it is a documentation site with no compiled or executed source. No change to either.

Documentation impact

This is the documentation change. Downstream effects:

  • Three pages shrink substantially; quickstart-saas.md no longer contains a runnable walkthrough, so readers arriving for one are routed to the SDK docs and the examples repo, which own that content. This also brings the pages back in line with the repo's stated convention that the hub orients rather than restating component docs.
  • The SaaS pages' (Coming soon) labels in SUMMARY.md remain accurate and were left unchanged.
  • Four cross-references on pages outside this ticket's ownership now describe cloud-deployment.md sections that no longer exist. They are prose descriptions, not anchors, so no link breaks — but they are stale. Listed below.

Claim impact

Removed or downgraded in this PR (file → before → after → reason):

File Before After Reason
quickstart-saas.md "works on any tier"; "Choose the tier that matches your team size" Tier framing removed No purchasable tiers exist
quickstart-saas.md "complete governance, policy evaluation, and audit logging at production grade run in the AI Agent Assembly cloud" Removed Present-tense claim about an unrun service; "complete" is an unqualified absolute
quickstart-saas.md "Govern a LangChain agent in under 5 minutes"; "fully governed"; "takes under 30 seconds" Removed Unmeasured duration claims
quickstart-saas.md AA_WORKSPACE_ID "from Settings → Workspace", AA_API_KEY "from Settings → API Keys" Removed Credential-issuance screens are not reachable
quickstart-saas.md gateway_url defaulting to a managed endpoint; "In the console, open Policies → New Policy" Removed Endpoint does not serve; console is not reachable
quickstart-saas.md latency=2ms in a sample runtime log Removed Unmeasured performance figure presented as observed
quickstart-saas.md Pro / Business / Enterprise tier sections: 10/50/unlimited agents, 30/90-day and 1-year retention, community-forum and business-hours and 24/7 support, 24h and 4h response, dedicated SRE, 99.9% SLA Removed No plans, no quota enforcement, no support function, no availability measurement
quickstart-saas.md "~10 minutes"/"~30 minutes" onboarding; "1–3 weeks" procurement with a week-by-week table Removed Unmeasured durations; procurement process is not running
quickstart-saas.md Signup at app.agent-assembly.com/signup, contact-sales form, community.agent-assembly.com Removed Endpoints do not serve
quickstart-saas.md Region selection during workspace setup; "dedicated region (data residency)" Removed No regions exist
quickstart-saas.md Stripe card entry on a Billing page; SE-issued Order Form; DPA/BAA templates; SOC 2 / HIPAA / GDPR requirement capture Removed No payment integration, no legal instruments, no assessed compliance posture
cloud-deployment.md Three-region table with locations and "Data at rest and in transit stays within the selected region" Removed No regions; no residency control to guarantee
cloud-deployment.md "Region selection is permanent — contact support to migrate" Removed Implies an operations team and a migration runbook
cloud-deployment.md Tenant-creation paths (self-serve vs SE-provisioned); tenant ID format ws-<uuid4> Removed Provisioning paths and the identifier format are not what the product produces
cloud-deployment.md Per-tier quota table (agents / policies / retention across Free, Pro, Business, Enterprise) Removed Published numbers not enforced per tier
cloud-deployment.md SSO sequence diagram plus SAML 2.0 and OIDC configuration walkthroughs, ACS/Entity ID, attribute map, "Settings → Authentication → SSO" Removed Console path not reachable; protocol support must be confirmed per protocol, not asserted as a pair. Re-authoring as a labelled design preview is AAASM-5613
cloud-deployment.md SCIM supported-operation matrix (7 rows, all ✅) and configuration steps incl. /scim/v2 endpoint Removed Per-operation support was not verified operation by operation
cloud-deployment.md Role table (Owner/Admin/Developer/Viewer) and IdP group-to-role mapping Removed Role names and permissions did not match the product
cloud-deployment.md Budget console form (team name, token limit, cost limit, hourly/daily/weekly/monthly window, deny/suspend) Removed; readers pointed at Policy reference → budget The form did not exist and its fields contradicted the budget schema this hub already documents as enforced
cloud-deployment.md SLA table (best-effort / 99.5% / 99.9%; community, 24h business-hours, 4h any-time; dedicated SRE) and "SLA credits apply" Removed No measured availability, no support function, no published terms
cloud-deployment.md Stripe monthly card billing, emailed invoices, payment-method management; net-30 invoicing, PO numbers, wire/ACH Removed No payment integration; contractual payment terms with no published contract
cloud-deployment.md BAA for HIPAA and DPA for GDPR "should request these during the SE call", "countersigned by the legal team" Removed Asserts specific legal instruments and a legal operation that do not exist
open-core-boundary.md "covered by the AAA-Commercial license and available on paid SaaS tiers" Downgraded to planned; licence-terms-not-published stated Licence terms published nowhere; nothing purchasable
open-core-boundary.md "The complete feature set stays SaaS… enterprise operations, the uptime SLA, and the managed compliance posture are delivered only through the cloud" Rewritten as design intent Present-tense SLA and compliance claim; "complete" absolute
open-core-boundary.md "every security control in the stack" "the security controls listed as Apache-2.0 below" Unqualified absolute → bounded reference
open-core-boundary.md Feature matrix rows: 99.9% uptime SLA, dedicated SRE, business-hours and 24/7 support, shared/dedicated SaaS region, multi-region data residency, tamper-evident signed audit, retention "up to 1 year", SIEM export (JSON/CEF), RBAC "Full (Owner/Admin/Developer/Viewer)" Matrix replaced by a ships-today table and a Planned capability-area paragraph Each row asserted a defined commercial product
open-core-boundary.md "SaaS — shared region ✅ (Free/Team tier)" in the OSS column Removed Asserted a live free SaaS tier; nothing is deployed
open-core-boundary.md "long-retention tamper-evident audit logs" in the boundary rule "longer-retention and higher-assurance audit storage" Cryptographic-integrity claim with no named mechanism
open-core-boundary.md aa-gateway note: "enterprise features gated behind SaaS config" "always OSS" No such gate in the crate
open-core-boundary.md "The OSS codebase contains all interception and enforcement logic" Bounded restatement referencing the ships-today section Unqualified absolute
open-core-boundary.md Footer "Legal approver: @Legal-team" Standard reviewed-by line plus an explicit no-licence-grant note Attributed legal sign-off to content that has since changed

| security-model.md | Compliance table: "SOC 2 Type II — In preparation (target: Q3 2026)"; "ISO 27001 — Roadmap"; "GDPR — DPA available on request"; "CCPA — Covered under SaaS Data Processing Agreement" | Table removed; replaced with a plain statement that no certification is held and no DPA or BAA is available | No audit report, assessment scope, or executed template exists. Removed rather than relabelled Planned — a status table in a compliance section reads as a programme with a trajectory, and a target date reads as a commitment |
| security-model.md | "Every agent action (policy check, event record, budget debit) produces an immutable log entry" | The verified properties: hash-chained JSONL, append-only by convention, best-effort emission, budget debits not audited | Two prohibited absolutes, and contradicted by the source on three counts — see Evidence |
| security-model.md | "Log entries are signed with HMAC-SHA256 using a log-signing key" | "There is no log-signing key, no HMAC, no signature, and no external anchoring over audit records" | False. The mechanism is an unkeyed SHA-256 hash chain; no hmac import exists in aa-core. This page's own cryptographic-primitives table has no audit-signing row — the two sections contradicted each other |
| security-model.md | "Logs are append-only; no delete or update API exists" | "Append-only by convention, not by constraint", naming the retention DELETE, the backfill UPDATE, and spill-buffer eviction | False. Retention pruning deletes audit rows in both drivers; no trigger, revoked grant, or WORM setting exists; the JSONL file is appended without fsync |
| security-model.md | "Log retention: configurable per tenant (default: 90 days)" | "An operator-set retention policy"; no default published | Per-tenant retention is a multi-tenant SaaS claim with no service behind it, and no 90-day default was found. The number is removed rather than corrected |
| security-model.md | "Logs are exportable in JSON or CEF format for SIEM integration" | "aasm audit export writes JSONL. There is no CEF output and no direct SIEM integration" | CEF is absent from the codebase entirely — zero occurrences. JSONL export is real, so the claim was narrowed rather than removed |
| security-model.md | Telemetry layer row: "append-only event log for every agent action" | Names the JSONL sink, the chain, the verify command, and the convention/best-effort bounds | The same claim restated in a summary table; fixed so the page does not contradict itself |
| security-model.md | STRIDE Repudiation cell: "Append-only audit log with tamper-evident signatures" | Names the unkeyed chain, the unprotected DB mirror, unaudited budget debits, and states repudiation cover is partial | "Signatures" is the HMAC claim restated inside the threat model |
| security-model.md | CTA: "Talk to the team about the STRIDE model, tamper-evident audit, and your compliance path" | Points at the audit log's actual properties; states plainly that no certification, DPA or BAA is available | Restated both unsupported claims in a sales context, where reliance is most likely |

| security-model.md | Identity layer: "operator authentication via SAML 2.0 / OIDC SSO"; STRIDE Spoofing: "SAML/OIDC token validation on every request"; an entire "Operator to console (SAML/OIDC)" section with a sign-in sequence diagram | SSO removed; replaced with the two mechanisms that ship (API key, JWT) under a Planned label telling readers not to plan an IdP integration against it | Zero occurrences of saml/oidc/openid in aa-api/src, aa-gateway/src, aa-auth/src; no console exists. Contradicted open-core-boundary.md in this same PR |
| security-model.md | STRIDE Repudiation: "Every call logged…" (SDK), "All intercepted requests logged by proxy before forwarding", "All mutating API calls logged with actor identity" | Each bounded to verified behaviour | Three surviving completeness claims that contradicted this PR's own checklist register. The proxy row now distinguishes ProxyAuditEntry (separate JSONL stream, not hash-chained, lossy on write failure) from the gateway's log; the REST row notes that under AuthMode::Off the actor is a synthetic admin caller |
| security-model.md | Secrets management: AES-256-GCM at rest, master key in a "SaaS control plane HSM", "never written to disk in plaintext", "never logged", console-driven rotation — plus a Vault crypto-table row and the IronClaw Vault layer row | Whole block removed; replaced with an explicit "do not treat this stack as a secret store" warning | No aes/gcm implementation in any aa-*/src (the single match is a ChaCha CSPRNG comment) and no aes-gcm dependency; no HSM or KMS reference anywhere; no console. Two bullets were also unqualified "never" guarantees. Most consequential removal on the page — a reader could have concluded the stack safely stores their LLM API keys |
| security-model.md | "Kernel event timestamps are monotonic; cannot be retroactively altered" | Bounded — a monotonic clock prevents reordering via wall-clock change, not post-hoc edits | Conflated a clock property with record integrity, in the Repudiation column where the distinction decides whether the control is load-bearing |
| security-model.md | "No MD5, SHA-1, or DES primitives are used anywhere in the stack" | Bounded to the primitives in the table and to first-party workspace code, with the limit stated | Unverifiable as written — "anywhere in the stack" reaches the whole transitive dependency tree, which this hub does not audit |
| security-model.md | Related documentation: "Cloud deployment — SSO configuration, SCIM provisioning"; "Open core boundary — which security features are OSS vs. enterprise" | Descriptors rewritten; checklist and source-of-truth added | Both advertised content this PR deleted. lychee cannot catch this class — the target resolves; the descriptor is what lies |
| self-host-observability.md | "The uptime SLAs (99.5% / 99.9%), on-call rotation, and managed compliance posture described in Cloud Deployment apply to the cloud only" | "There is no availability, on-call, or compliance commitment from anyone" — for either deployment | A regression this PR introduced. Cloud Deployment no longer describes them, so the two SLA figures this ticket set out to delete survived only here |

| glossary.md | AES-256-GCM — "used here to encrypt stored secrets at rest"; the IronClaw entry listing Vault among five layers with no caveat | Term retained carrying its own retraction: "AI Agent Assembly does not use it"; Vault labelled aspirational | The glossary was the last place in the hub asserting the vault is real, and a glossary is where a reader goes to confirm a term is a product concept rather than marketing. Wording matched to security-model.md so two pages cannot describe the same non-existent mechanism differently |
| glossary.md | Ed25519 — "used here to sign agent identity tokens" | "One-time possession proof over a server-issued nonce at registration"; notes the ongoing credential is a random token | Misdescribed the mechanism's role, implying a reusable signing credential |
| glossary.md | HMAC-SHA256 — "used to sign audit-log entries and webhook payloads" | Names the JWT and webhook uses; states explicitly it is not used on audit-log entries | The webhook half was right, the audit half is the disproved log-signing claim |
| glossary.md | Audit log — "append-only record of every agent action (policy checks, events, budget debits)"; "tamper-evident signed (HMAC-SHA256) is an Enterprise capability" | States the four bounds: unkeyed chain, unverifiable DB mirror, append-only by convention, best-effort emission with budget debits unaudited | Four errors in two sentences — "every", budget debits, HMAC signing, and an Enterprise framing for a mechanism that partly ships in the OSS build |
| glossary.md | SSO / SAML 2.0 / OIDC — "used to let operators log in with an enterprise identity provider"; SCIM unlabelled beside it | Both carry the Planned label; SSO names what operators actually authenticate with | Present tense for an unimplemented capability. SCIM made no direct claim but would have read as available by contrast with a corrected neighbour |

| security-model.md | IronClaw Boundary: eBPF sensor "catches kernel-level bypass attempts" | Names the platform (Linux only), separates observation from enforcement, and states the kill-after-syscall race | Prevention inferred from observation, unbounded by platform. The SSL uprobes are observe-only and blind to Go, rustls, Node/BoringSSL, GnuTLS, NSS and LibreSSL; the one enforcing probe SIGKILLs after the offending syscall has executed, and never inspects unmonitored PIDs |
| security-model.md | IronClaw Policy: rules "evaluated by the gateway policy engine before every agent action" | Bounded to the paths that reach the gateway | handle_llm_mitm (aa-proxy/src/proxy/mod.rs:806-966) has zero gateway references and refuses locally via in_tunnel_deny_reason; the gateway is reached only from handle_non_llm_mitm (:596, passed at :1098) and evaluate_mcp_request (:409). Under the llm_only default, non-intercepted hosts are transparently tunnelled and evaluated by nothing |
| security-model.md | STRIDE: eBPF "only reads SSL buffers; no access to unrelated memory regions" | Names what the probes actually read | False. Exec tracepoints capture pid, ppid, uid and filename on every execve, fork/clone is traced, and a syscall tracepoint sees syscall numbers. Understating a sensor's reach is the wrong error in an Info Disclosure cell |
| security-model.md | STRIDE: "RBAC on all administrative API endpoints" | Qualified to "only when auth is enabled" | Contradicted its own row: the gateway REST/admin surface is bypass-by-default and under AuthMode::Off every guarded route resolves to a synthetic admin caller |
| security-model.md | STRIDE: "secrets never logged" | A redaction scanner with finite detection coverage — a mitigation, not a guarantee | The same unverifiable absolute already removed from Secrets management |
| security-model.md, glossary.md | "No secret store, encryption-at-rest, or key-management component ships today" | A store exists and is mounted, is always empty, nothing can populate it, and successful resolution returns plaintext to the caller | My own understatement, corrected. InMemorySecretsStore is wired at aa-api/src/state.rs:449 and aa-gateway/src/server.rs:693/:850, all empty; no registration route in openapi/v1.yaml, no aasm secrets command, so every placeholder yields UnknownPlaceholder (422 / FailedPrecondition). Where resolution succeeds the resolver hands post-substitution args back to the caller, so the agent holds the raw credential. Narrower and more damning than the negation |
| security-model.md, glossary.md | HMAC-SHA256 "signs outbound webhook payloads" | Inbound verification of provider-signed webhooks; no outbound signing path exists | Directionally backwards, and efd28b7 propagated it from a table row I was not correcting into a page I own. All implementations are inbound (aa-devtool-saas/src/signature/{claude_ai,chatgpt,cursor}.rs, wired at aa-api/src/routes/devtools/mod.rs:142). aa-gateway/src/alerts.rs cannot be the signer — it is a SecretAlert struct with no delivery code |

| security-model.md | STRIDE: "Internal-only gRPC endpoint; never exposed directly" | States that reachability is the only control and that it is the operator's to enforce | The strongest finding on the page. Default bind is loopback (aa-gateway/src/main.rs:73), but --listen binds anywhere, the transport is plaintext, and PolicyService + AgentLifecycleService are mounted with enrich_interceptor (server.rs:721, :729), which is Ok(req) unconditionally (iam/grpc_auth.rs:150-160). Only audit/approval/topology/secrets/invalidation get the fail-closed interceptor. Anyone who can reach the port can call policy evaluation and agent registration unauthenticated |
| security-model.md | STRIDE: "Credential-token interceptor validates every agent-plane gRPC call" | Names the five fail-closed services and the two that never reject | Same row, same fact — the parenthetical already listed the fail-closed set, contradicting its own "every" |
| security-model.md | STRIDE: "HTTPS-only; HSTS enforced" | 🗺️ Not provided by this software — it serves HTTP; TLS termination and HSTS are the deployment's job | Infrastructure that is not deployed, in the present tense. Removed and labelled, not softened |
| security-model.md | STRIDE: "DDoS mitigation via upstream load balancer" | Per-key rate limiting in aa-auth is real; 🗺️ there is no DDoS mitigation and no load balancer | Same — described infrastructure that does not exist |
| security-model.md | STRIDE: "Tenant isolation enforced at API layer; cross-tenant access rejected" | 🗺️ Not enforced at the API layer | Contradicted by aa-api/src/routes/mod.rs:107: the gate verifies an API key or JWT, and "per-handler scope and tenant checks remain the handler's responsibility" |
| security-model.md | STRIDE: "Per-host CA pinning prevents MitM spoofing by agents" | Framing inverted: the per-host cert stops a third party impersonating the proxy to an agent that trusts the CA | It does nothing about an agent declining to route through the proxy — which is the threat the wording implied it covered |
| security-model.md | STRIDE: "certificate validation on every upstream" | Bounded to connections the proxy dials | skip_upstream_tls_verify swaps in an accept-any verifier (proxy/mod.rs:384, integration tests), and hosts transparently tunnelled under the llm_only default are never terminated, so the proxy validates nothing on them |
| security-model.md | STRIDE: "Input validation on all RPCs" | Per-service decoded-message-size caps, plus fail-closed unknown-key rejection in policy documents | Real controls, but not blanket validation of every RPC |

| security-model.md | Boundary layer: the eBPF syscall guard described as enforcing, with no mention that it is off | "Opt-in and disabled by default", naming both conditions required to enable it | Off-by-default control described as active. plan_control_ops (aa-runtime/src/ebpf_control.rs:103-122) plans the guard only when a confine-target PID is configured and the policy lowers to a non-empty allowlist — deliberately, since an empty allowlist would SIGKILL the confined process on its next syscall. A reviewer would otherwise credit a deployment with post-escape containment it never turned on |
| security-model.md | Boundary and Policy layers: the proxy "enforces an egress allowlist" / "applies a local in-tunnel egress allowlist" | Both now say the allowlist is empty by default — default-open, paired with the SSRF guard that always applies | Same class. aa-proxy/src/config.rs:79-92: "When empty (the default), no allowlist filter is applied"; denied_hosts is empty too; in_tunnel_deny_reason_empty_allowlist_is_default_open pins it. The always-on control is the SSRF guard, which config.rs:183 confirms a production binary can never relax |
| security-model.md | STRIDE: "Scope is limited to these probes" after listing SSL, exec and syscall probes | Enumerates the file-I/O kprobes and their 256-byte path field, process-exit events, and which probes load by default | A privacy claim, and false. aa-ebpf/src/kprobe.rs:145-160 registers fourteen file-I/O kprobe/kretprobe targets (openat, read, write, unlinkat, renameat2 + legacy) whose payload carries path: [u8; 256]full file paths (aa-ebpf-common/src/file.rs:88). TLS, file-I/O and exec all load unconditionally. A DPIA reviewer would have mis-scoped their assessment |
| security-model.md | "(Linux 5.3+)" | "Linux 5.8+ with BTF" | 5.3 is only the bpf_send_signal helper floor; the product requires 5.8+ with CONFIG_DEBUG_INFO_BTF=y (aa-ebpf/src/loader.rs:32), and ssl_probes.rs states "we require 5.8+" verbatim. The guard can never run on 5.3–5.7 |
| security-model.md | "blind to Go, rustls, Node/BoringSSL, GnuTLS, NSS and LibreSSL" | Blind to stacks that neither export SSL_read/SSL_write nor load as a libssl.so mapping — Go, rustls, GnuTLS, NSS, and statically-linked BoringSSL as in Node | My error. Discovery is a substring match on libssl.so (uprobe.rs:199) and attachment is by SSL_read/SSL_write symbol — LibreSSL is an OpenSSL fork exporting exactly those and installing as libssl.so.<ver>, so it is hooked. Node/BoringSSL is blind, but from static linking leaving no libssl.so mapping, not from symbol naming. Also notes that only SSL_read/SSL_write attach, so OpenSSL 3.x _ex callers are missed, and only the first matching mapping is used |
| security-model.md | "Query strings carrying credential-shaped values are redacted before logging" | Query strings are not logged at all — the tracing span records method, path and request id, and .path() excludes the query | Right outcome, wrong verb and wrong component: redact_target is aa-proxy's and is a no-op when its scanner is disabled. aa-api/src/middleware/tracing.rs never touches the query string |
| glossary.md | AES-256-GCM retraction carried no status marker | 🗺️ marker added | Consistency — SSO, SCIM and the Vault layer all carry one, so an emoji-scanning reader saw no marker on the term whose correction matters most |

| security-model.md | Proxy DoS: "Connection pool limits per agent; circuit breaker on upstream failure" | "Nothing bounds connection concurrency" | Neither mechanism exists. Over aa-proxy/src, case-insensitive: pool 0, circuit 0, breaker 0, semaphore 0, concurrency_limit 0 — against CertCache 11 from the same command, so the search reached the files. No tower dependency. The accept loop spawns an unbounded task per connection |
| security-model.md | Proxy Repudiation: "The proxy writes its own JSONL audit record per intercepted request" | 🗺️ No proxy audit file is produced by any shipped build | Unreachable in a release. ProxyServer::new hardcodes the sink to None, the emit path early-returns, ProxyConfig has no audit-path field, and the binary path (lib.rs:70) uses new. Every caller passing Some(...) is a test. An auditor would look for a file no deployment produces |
| security-model.md | Proxy EoP: "Proxy runs as unprivileged user; no write access to host filesystem" | PR_SET_DUMPABLE=0 on Linux (best-effort); running unprivileged and restricting writes are deployment responsibilities | harden_process sets only the dumpable flag, blocking same-uid ptrace and core dumps. It does not enforce either published property |
| security-model.md | Agent-plane auth, stated three inconsistent ways — including "anyone can call policy evaluation and agent registration unauthenticated" | One accurate statement in all three places, plus a dedicated callout block | This one was false in the dangerous direction — the previous round over-corrected. register enforces a did:key binding (Unauthenticated unless the agent_id's did:key encodes the supplied public key), a single-use identity-bound nonce, and an Ed25519 possession proof; CheckAction rejects a missing or mismatched token for a registered agent. The real gap is narrow: an unregistered caller with an empty token gets an evaluation, tenancy neutralised to anonymous |
| security-model.md, glossary.md, saas-claim-publication-checklist.md | "per-session JSONL" audit files (three pages) | "A shipped gateway writes one fixed gateway-default.jsonl" | Both shipped serve paths call setup_audit("gateway", "default", …) while audit_file_path formats {agent_id}-{session_id}.jsonl. The per-session capability exists and is never exercised. Operational, not cosmetic: retention, rotation and per-tenant separation planned around per-session files do not exist |
| security-model.md | REST Tampering: "OpenAPI schema validation rejects malformed inputs" | serde deserialisation into typed extractors; utoipa generates the spec, CI checks drift | Effect partially holds, mechanism wrong. No validation middleware: jsonschema 0, openapiv3 0, validate_request 0, schema_validat 0, against serde 487 / utoipa 182. Schema-only constraints (minLength, pattern, enum) hold only where a handler hand-wrote a check |
| security-model.md | Budget caps as an active DoS control (two cells) | Enforced only where a policy declares a limit | The same default-open shape as the egress allowlist two cells away. budget is Option, the tracker starts with no team limit, enforcement is limit-gated, and no shipped policy example sets onepolicy-examples/audit-only.yaml says so outright |
| security-model.md | eBPF Spoofing: "loaded only by privileged system service" | Adds that the daemon is not in the release artifact set | aa-ebpf-loaderd is listed in UNRELEASED_BINARIES; release.yml builds only aa-cli, aa-gateway, aa-runtime, aa-api (+aa-proxy on Linux). The eBPF layer cannot be activated from a released build, and without the daemon socket the runtime silently degrades to SDK-only rather than erroring |
| security-model.md | SDK Tampering: "SDK integrity verified by Cargo/npm/PyPI package hash" | Attributed to the registry, not to this product | A registry control over the download; nothing here verifies the SDK at runtime |
| security-model.md | Proxy Info Disclosure | Adds that the credential scanner is on by default but in RedactOnly mode, and under llm_only only built-in LLM hosts are scanned | Scope clause the cell lacked |

| security-model.md | eBPF EoP: "Loaded via CAP_BPF only; capability is dropped after program load" | The unit grants CAP_BPF and CAP_PERFMON, and the daemon retains them for its lifetime; states the privilege separation that does hold | Both halves false. Exhaustive search of aa-ebpf/src against a positive control of Ebpf = 356: drop_capabilit 0, caps:: 0, capctl 0, prctl 0, PR_CAPBSET 0, setresuid 0, cap_drop 0, SECBIT 0; only libc in deps. The sole mentions are systemd-unit doc comments (bin/loaderd.rs:26-27) that bound the set at launch. It also cannot be true by construction — control/mod.rs:5-7 describes a long-running daemon that "owns every aya::Ebpf handle and holds the only CAP_BPF / CAP_PERFMON" and services further load requests, so dropping after the first load would break it |

Residual claims found in files this ticket does not own — for AAASM-5528 / AAASM-5613:

File:line Claim Suggested owner
comparison.md:19 Names the "AAA-Commercial (Enterprise) tier and the paid SaaS platform" AAASM-5528
comparison.md:55 "Immutable audit log with tamper-evident signatures ✓ 🚧 (HMAC-SHA256)" in a competitor matrix AAASM-5528
comparison.md:82 "Immutable tamper-evident audit log … signed with HMAC-SHA256, making post-hoc alteration detectable" AAASM-5528
README.md:45 "Set up SSO, SCIM, regions, and billing → Cloud deployment" — points at content this PR removed AAASM-5528
faq.md:38-39 "(tamper-evident audit, dedicated regions, SLAs) are commercial and delivered on paid SaaS tiers" — present tense Follow-up
docker-containers.md:374 "data residency, tamper-evident audit, and the uptime SLAs run only in the…" — present tense Follow-up

source-of-truth.md is already correct — Cloud, Enterprise and Operations are all labelled 🔒 Private / 🗺️ Planned — and needed no change. This PR reuses its label vocabulary rather than inventing a second one.

Evidence

  • Endpoint reachabilityapp.agent-assembly.com/signup, app.agent-assembly.com/contact-sales, api.agent-assembly.com/, community.agent-assembly.com/ all return HTTP 530 (Cloudflare: no origin). Their DNS resolves only to a Cloudflare wildcard shared with arbitrary non-existent subdomains. agent-assembly.com/early-access returns 200, so the early-access CTA was retained.
  • Internal state review — the managed control plane and its infrastructure were reviewed directly to establish what exists before writing anything. Findings are deliberately not reproduced here: those repositories are private, and this is a public repo. What the review established is reflected in the diff only as the absence of unsupported claims, never as a statement about internal implementation.
  • Contradiction inside this hub — the removed budget console form specified per-team token and cost limits over hourly/weekly windows; policy-reference.md documents the enforced schema as per-agent and per-organisation USD limits on daily/monthly boundaries. Two published pages disagreed about the same feature; the one with a schema behind it wins.
  • Build-output verification — claims were re-grepped in docs/book/, not in the source, so the check covers what a reader actually receives. See Tests.

Evidence — audit-log claims verified against the source

The security-model corrections were checked against the Apache-2.0
agent-assembly tree, not
against the surrounding prose. Summary of what is actually there:

Claim on the page What the code does Verdict
"signed with HMAC-SHA256 using a log-signing key" An unkeyed SHA-256 hash chain in aa-core/src/audit.rsentry_hash over the entry's fields plus previous_hash. No hmac import in aa-core; the only HMAC in the repo is unrelated (webhook signature verification) False as written. A real mechanism exists, but it is keyless, so an actor who can rewrite the log can recompute a valid chain
Chain is verifiable Yes, for the JSONL sinkAuditWriter::verify_chain in aa-gateway/src/audit.rs, exposed as aasm audit verify-chain <file> (aa-cli/src/commands/audit/verify_chain.rs), which exits non-zero and names the first bad index True — and I initially under-stated this; see the correction commit
Chain covers the database No. The runtime→storage conversion explicitly drops seq, previous_hash, entry_hash, and neither audit_events (gateway migration + SQLite driver) nor audit_logs has a column for them Not verifiable from either table
"no delete or update API exists" DELETE FROM audit_events WHERE ts < … in both the Postgres and SQLite drivers (retention pruning); an UPDATE audit_logs backfill migration; spill-buffer eviction on cap. No trigger, REVOKE, or WORM setting anywhere in the migrations False. Append-only is a write-path convention
"Every agent action … produces a log entry" Fire-and-forget try_send onto a bounded channel; on backpressure the entry is dropped, counted in audit_drops, and the response still returns Ok. No fsync, so a crash before flush loses buffered entries False
"…(policy check, event record, budget debit)" BudgetLimitApproached / BudgetLimitExceeded appear only as enum variants, string mappings, read-side filter parsing, a proptest generator, and tests — never constructed. No debit emits an entry False for budget debits
Export "in JSON or CEF" ExportFormat has three variants — Csv, Json, Jsonl (aa-cli/src/commands/audit/models.rs) — plus a separate aasm audit compliance-export command. --format is required with no default. Zero occurrences of CEF anywhere False for CEF only. My first correction wrongly narrowed this to JSONL, understating what ships; fixed in 74fa595

One consequence worth flagging to reviewers, now documented on the page: because a
dropped entry still advances the chain head, verify-chain reports failure for a
gap caused by backpressure exactly as it would for a malicious edit. A verification
failure means "investigate", not "compromise".

Compliance evidence searched for and not found. No audit report, certificate,
assessment scope, or executed DPA/BAA template exists in either the public monorepo
or the private control-plane repo. The only in-repo hits were an ADR asserting the
posture is "clean" — a doc claim, not evidence — and an aasm audit export --compliance soc2 flag that prepends a metadata header to an export file, which is
a formatting option rather than an attestation.

Migration behavior

No migration. Documentation-only, no URLs removed, no pages deleted, no redirects needed:

  • All three pages keep their filenames, H1s and SUMMARY.md positions, so every existing inbound link and bookmark still resolves.
  • Four in-page anchors disappear with their sections (#sso-configuration, #scim-user-provisioning, #budget-configuration, and the tier anchors on the quick start). No page in this repo links to any of them — verified by grep — so no internal link breaks. An external deep link to one of those anchors will land on the page top, which now carries the Planned banner.
  • One page is added, so the About section gains an entry.

Rollback strategy

git revert any commit independently — the commits touch largely disjoint hunks and each was verified to build on its own. Two pairs are ordered: 62d3d9c supersedes 6a1f876, and 74fa595 supersedes part of 25e3197, so revert the later of a pair before the earlier. Reverting the whole PR restores the previous pages exactly.

Rolling back is not a neutral act here: it re-publishes the claims this PR removed, so it should be treated as a decision requiring the same approval the checklist demands for publishing them in the first place, not as routine remediation. If a specific removal turns out to be wrong, the intended path is to restore that one claim through the checklist with its evidence attached.

Known limitations

  • The pages are now thin. quickstart-saas.md no longer contains a walkthrough. That is the correct state while the service does not exist, and AAASM-5613 will add labelled design previews on top — but a reader arriving today gets orientation and links rather than a tutorial.
  • Residual claims remain elsewhere in the hub. Four, across three files outside this ticket's path ownership (comparison.md, faq.md, docker-containers.md) — down from twelve, as security-model.md, self-host-observability.md and glossary.md came into scope and were fixed here.
  • One commit corrects an earlier commit in this same PR. 62d3d9c fixes 6a1f876, which both under-stated the audit chain (calling it unverifiable when aasm audit verify-chain ships) and repeated a false claim that budget debits are audited. The correction is a follow-up commit rather than a rewrite, so the mistake stays visible in review.
  • Pagefind was not run locally. Full aggregation clones five module repos. The mdBook search index was scanned instead, and Pagefind indexes the same verified HTML.
  • The zh-Hant catalog holds stale translations of removed strings. They are unreachable and the built zh-Hant pages were verified clean; a msgmerge pass would mark them obsolete.
  • markdownlint is not wired into this repo's CI, so the informational findings above are unenforced either way. The new page matches the existing table and footer style, including its pre-existing rule violations.
  • The checklist's approval owners are roles, not people. Someone has to map those roles to named individuals before the first restoration request; the page deliberately does not guess at names.

Managed-service operational and contractual claims were published on this hub
without a running service, an owner, or evidence behind them. Removing them once
does not stop them returning, so record per claim class why it was removed, who
must approve restoring it, and the concrete evidence that approval requires.

Registered in SUMMARY.md in the same commit because an unregistered page does not
render, and the pages that link to it land in the following commits.

Refs: AAASM-5612
…rough

The page instructed readers to sign up at a URL that does not serve, copy a
workspace ID and API key out of console screens that are not reachable, and point
an SDK at a gateway endpoint that returns no origin. It then presented three
purchasable tiers with agent quotas, retention periods, region selection,
onboarding-duration estimates, support-response times, a week-by-week procurement
timeline, and DPA/BAA and compliance handling — none of which has a running
service, an approved commercial policy, or a legal review behind it.

Removed rather than reworded: softening "99.9% SLA" into "high availability" does
not reduce what a reader takes away, it only makes the claim harder to audit.

What replaces it is a statement of what actually ships (the Apache-2.0 stack and
its existing docs), an explicit list of what this hub will not publish and why,
and a link to the publication checklist that gates each item.

Refs: AAASM-5612
…n unrun service

The page documented the managed control plane as if it were operating: a region
list with a data-residency guarantee, tenant provisioning paths, a tenant-ID
format, per-tier quotas, SSO and SCIM walkthroughs with console navigation and
endpoints, a console budget form, an availability-and-support SLA table with
service credits, card and invoice billing setup, and DPA/BAA handling.

None of it is backed by a running service. Several items were also internally
inconsistent with what this hub already documents — the budget form's fields do
not match the budget schema the gateway enforces in the policy reference, which
is the source of truth for budget behaviour.

The page now states what the open-source stack covers instead, enumerates what
will not be published and why, and links the publication checklist.

Refs: AAASM-5612
…ability claims

The page's framing asserted that enterprise operations "are covered by the
AAA-Commercial license and available on paid SaaS tiers", that the complete
feature set "is delivered" through the cloud, and that an uptime SLA and a
managed compliance posture exist. None of that is true: no tier is purchasable,
the licence terms are not published, and the managed service is not running.

Also drops two unqualified absolutes that the page could not support — "every
security control in the stack" and "the complete feature set" — and stops naming
tamper-evident audit as a settled capability in the boundary rule, since that is
a cryptographic-integrity claim needing a named mechanism.

Refs: AAASM-5612
… and region claims

A two-column tick matrix presents the commercial column as a defined product even
under a "coming soon" banner. This one asserted a 99.9% uptime SLA, 24/7 and
business-hours support tiers, a dedicated SRE contact, dedicated and shared SaaS
regions, multi-region data residency, tamper-evident audit, SIEM export in named
formats, and audit retention "up to 1 year" — and marked shared-region SaaS as
already available on the open-source side, which is not the case anywhere.

Split into two sections instead: what ships in the Apache-2.0 core today, each row
pointing at the page that documents it, and what is intended for the commercial
tier, stated as capability areas with no plan, price, quota, retention period,
region, availability or compliance claim attached.

Refs: AAASM-5612
… claims

Three legal-adjacent statements had nothing behind them. The page named an
"AAA-Commercial license" whose terms are published nowhere. It stated that
enterprise features are gated behind SaaS config in a crate that has no such gate,
and that the OSS codebase contains "all" interception and enforcement logic — an
unqualified absolute. And the footer attributed legal sign-off to a page whose
content has changed repeatedly since that sign-off was recorded.

Replaces the footer attribution with the hub's standard reviewed-by line plus an
explicit note that the page grants no licence beyond Apache-2.0, and adds the
publication checklist to the related-documentation list so the next maintainer
finds the gate before re-adding a commercial claim.

Refs: AAASM-5612
…DPA availability

The compliance posture table asserted "SOC 2 Type II — In preparation (target:
Q3 2026)", "ISO 27001 — Roadmap", a GDPR row offering a DPA "on request", and a
CCPA row claiming coverage under a SaaS Data Processing Agreement.

No audit report, assessment scope, or executed legal template exists for any of
these. Searched for one: the only in-repo hits are an ADR asserting the posture is
clean, which is a doc claim rather than evidence, and an `aasm audit export
--compliance soc2` flag that prepends a metadata header to an export file, which
is a formatting feature rather than a certification.

Removed the table rather than relabelling the rows Planned. A status table inside
a section headed "Compliance posture" reads to a procurement reviewer as a
programme with a trajectory — relabelling would have left exactly the softer
promise this ticket exists to eliminate. Replaced with a plain statement that no
certification is held and no DPA or BAA is available.

Refs: AAASM-5612
…nce claims

The page asserted that every agent action "produces an immutable log entry", that
entries "are signed with HMAC-SHA256 using a log-signing key", and that "no delete
or update API exists". Verified each against the Apache-2.0 source rather than
against the surrounding prose. Findings:

- There IS a real integrity mechanism, but it is a KEYLESS SHA-256 hash chain in
  aa-core/src/audit.rs, not an HMAC. No hmac import exists in aa-core, and this
  page's own cryptographic-primitives table has no audit-signing row — the two
  sections contradicted each other.
- The chain hashes are NOT persisted. The audit_events schema (Postgres migration
  and SQLite driver) has no entry-hash, previous-hash, or sequence column, so the
  chain cannot be re-verified from the durable store.
- Deletion IS implemented: retention pruning runs DELETE FROM audit_events in both
  drivers. No trigger, revoked grant, or WORM setting prevents it.
- Emission is best-effort: entries go to a bounded channel via a non-blocking
  send, and one call site discards the error outright.

Replaces the three bullets with the verified properties, and fixes the same claim
where it was restated in the Telemetry layer row and the STRIDE Repudiation cell,
so the page no longer contradicts itself.

Refs: AAASM-5612
The page stated retention is "configurable per tenant (default: 90 days)" and that
logs are "exportable in JSON or CEF format for SIEM integration".

Per-tenant retention is a multi-tenant SaaS claim with no managed service behind
it; the actual mechanism is an operator-set RetentionPolicy applied by the storage
driver, and no 90-day default was found. The number is removed rather than
corrected — an unmeasured default is not worth publishing.

CEF is absent from the codebase entirely: zero occurrences across the CLI, gateway,
API and core docs. JSONL export is real (`aasm audit export`, ExportFormat::Jsonl),
so the claim is narrowed to that and the "SIEM integration" phrasing dropped, since
no integration exists — only a file a SIEM could ingest.

Refs: AAASM-5612
…y-access CTA

The CTA invited readers to "talk to the team about the STRIDE model, tamper-evident
audit, and your compliance path" — restating, in a sales context, the two claims
this page no longer supports: an audit integrity guarantee stronger than the
keyless unpersisted hash chain that exists, and a compliance path with nothing
behind it.

Keeps the STRIDE offer, points at the audit log's actual integrity properties, and
states plainly that no certification, DPA or BAA is available. Also refreshes the
reviewed-on date, since the page's claims changed materially.

Refs: AAASM-5612
…list

Adds seven rows for the claims removed from the security model, and sharpens the
existing tamper-evidence row now that the mechanism has been read rather than
inferred.

Three of the new rows are marked as contradicted by the Apache-2.0 source rather
than merely unevidenced, and their evidence column says so: restoring them needs a
code change (a keyed construction, an enforced storage constraint, a fail-closed
emission path), not an owner's approval. That distinction matters — an approver
handed one of these rows should not be able to sign it off.

Also records that a framework-named export flag is not compliance evidence, and
adds a language rule: verify a claim against the code, not against the neighbouring
prose that repeats it.

Refs: AAASM-5612
… of the sinks

My previous commit was wrong in two directions and is corrected here rather than
rewritten, so the record of the mistake survives review.

Under-stated: I described verify_integrity/verify_chain as in-memory only and said
the chain "cannot be re-verified from the durable store". In fact the primary sink
is a per-session JSONL file that IS hash-chained and IS verifiable today by an
operator running `aasm audit verify-chain <file>`. The non-verifiable part is the
database mirror, whose runtime-to-storage conversion deliberately drops seq,
previous_hash and entry_hash. Understating a real control is still inaccurate.

Over-stated: I wrote that the gateway records "policy decisions, event records,
budget debits". Budget debits are NOT audited — BudgetLimitApproached and
BudgetLimitExceeded exist in the enum, the string mapping, the read-side filter and
the tests, but are never constructed anywhere, so no debit produces an entry.

Also adds three properties the fuller read surfaced: an UPDATE has been issued
against audit_logs by a backfill migration, the offline spill buffer evicts its
oldest events on cap, and a dropped entry still advances the chain head — so
verify-chain reports failure for backpressure exactly as it would for tampering,
which a reader needs to know before treating a failure as compromise.

Refs: AAASM-5612
…g behaviour

Updates the audit rows now that the sinks have been read in full, and adds two
rows the fuller read justified: chain coverage stated without naming the sink, and
audit coverage listed by event category.

The tamper-evidence row no longer says the claim is unrestorable. The mechanism is
real and operator-verifiable for the JSONL sink, so the row now permits the phrase
provided its three bounds — unkeyed, JSONL-only, says nothing about completeness —
appear in the same sentence. Gating a true claim as if it were false would make the
checklist a source of understatement.

Adds two language rules for the same reason: understating a control is also
inaccurate, and a control should name the scope it covers.

Refs: AAASM-5612
…tor auth path

The page advertised SSO three times: the Identity layer row claimed "operator
authentication via SAML 2.0 / OIDC SSO", the STRIDE Spoofing cell for aa-api claimed
"SAML/OIDC token validation on every request", and an entire "Operator to console
(SAML/OIDC)" section carried a sign-in sequence diagram against a hosted console.

None of it exists. Zero occurrences of saml, oidc or openid across aa-api/src,
aa-gateway/src and aa-auth/src; aa-auth/src contains only api_key, config, error,
gate, jwt, lib, password, rate_limit, role and scope modules. There is also no
console to sign in to.

This also contradicted two pages in this same PR: open-core-boundary now lists SSO
as planned for an unavailable tier, and the SSO walkthrough was removed from
cloud-deployment for this exact reason. Missed here because security-model entered
scope late and I reviewed only the sections I was already editing.

Replaces the section with the two mechanisms that do ship — API key and JWT — under
a Planned label that tells a reader not to plan an IdP integration against it.

Refs: AAASM-5612
…aims

The STRIDE Repudiation column still asserted total logging coverage in three rows
after I corrected only the Gateway row: "Every call logged with agent ID and
timestamp" (SDK), "All intercepted requests logged by proxy before forwarding"
(proxy), and "All mutating API calls logged with actor identity" (REST API).

The page therefore contradicted its own publication-checklist register, which
already lists the generic form as contradicted by the source.

Each is now bounded to verified behaviour. The proxy row also gains a distinction
the page never drew: the proxy emits ProxyAuditEntry to its own JSONL stream, which
is explicitly NOT the gateway's hash-chained AuditEntry and loses a line on write
failure rather than stopping. The REST row notes that under AuthMode::Off the
recorded actor is the synthetic admin caller, so "actor identity" is not a real
identity in that posture.

Refs: AAASM-5612
…ships

I replaced an overstated export claim with an understated one. The page said
"Export is JSON Lines, via the CLI. `aasm audit export` writes JSONL."

ExportFormat has three variants — Csv, Json, Jsonl (aa-cli/src/commands/audit/
models.rs) — dispatched in export.rs. `--format` is a required argument with no
default, so a reader following that sentence literally hits a clap error rather
than an export. A second command, `aasm audit compliance-export`, was omitted
entirely.

The checklist row compounded it by listing JSON export among removed unevidenced
claims when JSON export ships; that row is now scoped to CEF and to "SIEM
integration" as a managed capability, and says explicitly that CSV/JSON/JSONL must
not be swept up in it.

Only the CEF half of the original finding was right. This is the second
understatement in this ticket, after 6a1f876: both came from writing the conclusion
straight out of a targeted grep for the false part of a claim without reading the
surrounding enum. The rule added to the checklist covers the class; this commit is
the second instance of it.

Refs: AAASM-5612
…oved sections

The block still advertised "Cloud deployment — SSO configuration, SCIM provisioning"
and "Open core boundary — which security features are OSS vs. enterprise". Both
descriptors point at content this PR deleted: the SSO/SCIM walkthroughs are gone and
the OSS-vs-enterprise tier matrix was replaced.

I updated this block on the other three pages and missed it here. Worth noting that
lychee cannot catch this class — the link target resolves fine; it is the descriptor
that advertises content the target no longer has.

Refs: AAASM-5612
… the hub

A regression this PR introduced. The scope callout said the uptime SLAs
(99.5% / 99.9%), on-call rotation, and managed compliance posture "described in
Cloud Deployment" apply to the cloud only — but Cloud Deployment no longer describes
any of them, so the two SLA figures this ticket set out to delete survived here and
nowhere else, in a page that reads as if they are real somewhere.

The callout now makes the stronger and true statement: there is no availability,
on-call, or compliance commitment from anyone, for either deployment. Also refreshes
two related-documentation descriptors that pointed at removed content.

Refs: AAASM-5612
…rnance ADR

As written this page is a locally-authored claims registry with invented governance
semantics — approval owners, evidence grades, a workflow — shipped ahead of
AAASM-5621, the ADR meant to define exactly those semantics org-wide. That is the
hazard 5621's problem statement names.

Adds a banner stating the ADR wins on conflict, that the page is expected to become
an adoption record feeding the AAASM-5531 capability/evidence manifest rather than a
standalone registry, and drawing the line between the two halves: the register of
what was removed and why is evidence and stands; the approval workflow around it is
a placeholder.

Refs: AAASM-5612
… claims

Found while re-scanning the whole page as the review required, not among the items
it named — reporting it for veto rather than sitting on it.

The Secrets management section described a managed vault: AES-256-GCM encryption at
rest, a master key held in the SaaS control plane's HSM, secrets never written to
disk in plaintext or logged, and rotation driven from a console. The IronClaw Vault
layer row and a Cryptographic primitives row repeated it.

None of it is present. No aes/gcm implementation across any aa-*/src (the single
match is a ChaCha CSPRNG comment in an IPC handshake) and no aes-gcm dependency; no
HSM or KMS reference anywhere; no console. Two of the bullets were also unqualified
"never" guarantees.

This is the most consequential removal on the page: a reader could have concluded
the stack safely stores their LLM API keys. Replaced with an explicit warning that
it is not a secret store and that protection at rest is the deployment's
responsibility.

Refs: AAASM-5612
Also found in the full-page re-scan, also outside the named review items.

"Kernel event timestamps are monotonic; cannot be retroactively altered" conflated
a clock property with a record-integrity property. A monotonic clock stops
reordering via wall-clock adjustment; it does nothing to stop a written record being
edited afterwards, and this sat in the Repudiation column where that distinction
decides whether the control is load-bearing.

"No MD5, SHA-1, or DES primitives are used anywhere in the stack" is unverifiable as
written — "anywhere in the stack" reaches the whole transitive dependency tree,
which this hub does not audit. Bounded to the primitives in the table and to
first-party workspace code, with the limit stated.

Refs: AAASM-5612
…ayer claim

After 9ce47fc removed the secret-vault claims from the security model, the
glossary was the last place in the hub asserting the vault is real — and a
glossary is exactly where a reader goes to confirm a term is a genuine product
concept rather than marketing.

The AES-256-GCM entry said it was "used here to encrypt stored secrets at rest".
It is not used at all: no aes/gcm implementation in any aa-*/src, no aes-gcm
dependency, no HSM or KMS reference. The IronClaw entry listed Vault among the
five layers without noting that layer ships nothing.

The term is kept rather than deleted, carrying its own correction, so a reader
who met the old claim elsewhere lands on the retraction instead of a missing
entry. Wording matches security-model.md so the two pages cannot describe the
same non-existent mechanism differently.

Also corrects the Ed25519 entry, which described it as signing "agent identity
tokens". It signs a one-time possession proof over a server-issued nonce at
registration; the ongoing bearer credential is a random token, not an Ed25519
signature.

Refs: AAASM-5612
Both entries restated claims disproved earlier in this PR.

HMAC-SHA256 was defined as signing "audit-log entries and webhook payloads". The
webhook half is right; the audit-log half is not — the audit mechanism is an
unkeyed SHA-256 hash chain and no log-signing key exists. The entry now names the
two places HMAC is genuinely used and says explicitly where it is not.

The Audit log entry claimed an "append-only record of every agent action (policy
checks, events, budget debits)" and framed a "tamper-evident signed audit log
(HMAC-SHA256)" as an Enterprise capability. That packs four errors into two
sentences: "every" (emission is best-effort), budget debits (never emitted),
HMAC signing (does not exist), and an Enterprise framing for a mechanism that
partly ships today in the OSS build. It now states the four bounds a reader is
most likely to assume away, and links the full statement.

Refs: AAASM-5612
The SSO entry said the two federation protocols are "used to let operators log in
with an enterprise identity provider" — present tense, and disproved in 03c7a30:
zero occurrences of saml/oidc/openid across aa-api/src, aa-gateway/src and
aa-auth/src, and no console to sign in to.

SCIM's definition was protocol-neutral and made no direct claim, but sitting
unlabelled next to a corrected SSO entry it would read as available by contrast.
Both now carry the same Planned label used elsewhere in this PR, and the SSO
entry names what operators actually authenticate with.

Refs: AAASM-5612
…e layer table

Two banned-class claims survived the earlier scan because neither contains a
listed token in a listed form. Matching vocabulary does not find them; reading
each table cell as a guarantee and asking what would have to be true does.

Boundary row claimed the eBPF sensor "catches kernel-level bypass attempts" —
prevention inferred from observation, with no platform bound. Verified in
aa-ebpf-probes/: the SSL uprobes are observe-only and blind to Go, rustls,
Node/BoringSSL, GnuTLS, NSS and LibreSSL, whose traffic never reaches them. One
probe does enforce (syscall_guard SIGKILLs a monitored process on a
non-allowlisted syscall, Linux 5.3+) — but its own header documents a
kill-after-syscall race: the offending connect/write/unlink lands once before the
task dies, and unmonitored PIDs are never inspected. The row now says so, and is
marked Linux-only. Note ssl_probes.rs still calls the proxy and syscall layers
"the catch-all" in a comment — the same error one layer down, not evidence.

Policy row claimed rules are "evaluated by the gateway policy engine before every
agent action". Re-derived: handle_llm_mitm (aa-proxy/src/proxy/mod.rs:806-966) has
zero gateway references and refuses locally via in_tunnel_deny_reason; the gateway
is reached only from handle_non_llm_mitm (:596, passed at :1098) and
evaluate_mcp_request (:409). Under the llm_only default, non-intercepted hosts are
transparently tunnelled and evaluated by nothing. Bounded to the paths that hold.

Refs: AAASM-5612
…-contradicting

Found by re-reading every cell as a guarantee. Fixing only the three that are
materially false or contradict a correction already on this page; the remaining
class findings are reported to the coordinator rather than fixed, because
rewriting the STRIDE table wholesale edges into the restructuring reserved for
AAASM-5605.

eBPF Info Disclosure said "eBPF only reads SSL buffers; no access to unrelated
memory regions". False: exec tracepoints capture pid, ppid, uid and filename on
every execve, fork/clone is traced for descendant tracking, and a syscall
tracepoint sees syscall numbers. Understating a sensor's data reach is the wrong
error to make in an Info Disclosure assessment.

Gateway Elevation of Privilege said "RBAC on all administrative API endpoints".
The same row already notes the REST/admin surface is off by default, and under
AuthMode::Off every guarded route resolves to a synthetic admin caller — so no
role check applies. The cell contradicted its own row.

SDK Info Disclosure said "secrets never logged" — the same unverifiable absolute
already removed from Secrets management. Bounded to what exists: a redaction
scanner with finite detection coverage, which is a mitigation, not a guarantee.

Refs: AAASM-5612
…and is worse

My own understatement, the fourth of this ticket, and the same failure mode: I
proved AES-GCM and HSM absent, then negated the whole category.

Re-derived. A SecretsStore trait with one implementation, InMemorySecretsStore,
IS mounted in both production constructions (aa-api/src/state.rs:449;
aa-gateway/src/server.rs:693 and :850). What is absent is everything that made the
original vault claim dangerous: no encryption at rest, no HSM or KMS, no
registration route in openapi/v1.yaml and no `aasm secrets` command — so both
constructions start empty, nothing can populate them, and every ${NAME} resolves
to UnknownPlaceholder (422 / FailedPrecondition).

The part that is worse than "nothing ships": where resolution does succeed, the
resolver returns the post-substitution arguments to the caller
(aa-api/src/routes/dispatch.rs), so the agent process holds the raw credential
rather than the gateway making the outbound call. The audit entry records
placeholder names only, so the credential stays out of the audit stream — but the
agent has it.

The operative advice is unchanged and deliberately unweakened: do not treat this
stack as a secret store. Both pages updated together so they cannot diverge. No
implication either way about whether the surface gets wired or gated.

Refs: AAASM-5612
…tion, not outbound signing

Confirmed backwards. The crypto table said HMAC-SHA256 "signs outbound webhook
payloads so receivers can verify authenticity", and efd28b7 copied that direction
into the glossary, so a pre-existing error in a row I was not correcting spread to
a page I do own.

Every implementation is inbound verification of provider-signed requests:
aa-devtool-saas/src/signature/{claude_ai,chatgpt,cursor}.rs, wired at
aa-api/src/routes/devtools/mod.rs:142 and run before the body is parsed.

Checked the outbound hypothesis specifically rather than inferring absence.
aa-gateway/src/alerts.rs cannot be the signer: it is a SecretAlert struct with
primary_kind/redacted_label and no delivery code at all. The only reqwest client
in the gateway is a local-mode probe. No x-signature emission or outbound signing
secret exists anywhere in aa-*/src, while the control search for hmac users
returns ten-plus files — so the probe sees HMAC fine and the absence is real.

Also corrects the rotation cadence: the shared secret is issued and rotated by the
sending provider, not on a 90-day cycle owned by this software.

Refs: AAASM-5612
…fied behaviour

Cells edited in place; no column, row or framing change.

**gRPC "internal-only; never exposed directly"** — a deployment property published
as a security property, and the strongest finding here. Default bind IS loopback
(aa-gateway/src/main.rs:73, 127.0.0.1:50051), but --listen binds anywhere, the
transport is plaintext, and PolicyService and AgentLifecycleService are mounted
with enrich_interceptor (aa-gateway/src/server.rs:721,:729), which is
`Ok(req)` unconditionally (iam/grpc_auth.rs:150-160) — it attaches a caller if a
token resolves and proceeds regardless if not. Only audit/approval/topology/
secrets/invalidation get the fail-closed `auth`. So anyone who can reach the port
calls policy evaluation and agent registration unauthenticated. Stated plainly.

**"validates every agent-plane gRPC call"** — same row, same fact, corrected with
it: two services never reject.

**"HTTPS-only; HSTS enforced" / "DDoS mitigation via upstream load balancer"** —
infrastructure that is not deployed, present tense. Removed and labelled, not
softened.

**"Tenant isolation enforced at API layer"** — contradicted by
aa-api/src/routes/mod.rs:107, which states per-handler scope and tenant checks
remain the handler's responsibility.

**"certificate validation on every upstream"** — true only where the proxy dials:
skip_upstream_tls_verify swaps in an accept-any verifier, and hosts transparently
tunnelled under llm_only are never terminated so nothing is validated.

**"CA pinning prevents MitM spoofing by agents"** — inverted the framing: the
per-host cert stops a third party impersonating the proxy to an agent trusting the
CA; it does nothing about an agent declining to route through the proxy.

**"Input validation on all RPCs"** — bounded to per-service message-size caps and
fail-closed unknown-key rejection in policy documents.

Refs: AAASM-5612
Chisanan232 added a commit that referenced this pull request Aug 6, 2026
The matrix row and differentiator 5 were wrong twice over, and this change
was written but never committed in the previous push.

Over-claimed: the chain is an unkeyed SHA-256 digest chain
(aa-core/src/audit.rs:10,713 — no hmac import exists anywhere in aa-core;
the only HMAC in the repo signs REST JWTs and webhooks). "HMAC-SHA256" is
wrong and "signed" is wrong — an unkeyed chain is tamper-evident, not a
signature. "Immutable" is wrong too: retention pruning deletes rows
(aa-gateway/src/storage/sqlite.rs:715, postgres.rs:854), and the DB mirror
carries no chain metadata at all (storage/audit_bridge.rs:10-12).

Under-claimed: the 🚧 marker presented this as planned Enterprise work. It
is not — AuditWriter::verify_chain (aa-gateway/src/audit.rs:142) ships today
as `aasm audit verify-chain` (aa-cli/src/commands/audit/mod.rs:14,31,44) in
the open-source build. Understating is as inaccurate as overstating, so the
capability is stated plainly rather than deleted.

Also corrects differentiator 2 ("kernel-level enforcement" -> visibility;
eBPF returns no verdict) and differentiator 3 (per-host CA -> per-host certs
from a local root CA, plus the launch/routing precondition).

Wording matches AAASM-5612 PR #134 on security-model.md so the hub does not
ship a page making this claim and a page declaring it contradicted.

Refs AAASM-5528, AAASM-5626
…the eBPF collection scope

Three controls described as if active are opt-in or default-open. This is the
class I caught for llm_only and gateway auth-off and then missed three times on
the same page: a real control, correctly described, with the default omitted.

**Syscall guard — opt-in, disabled by default.** plan_control_ops
(aa-runtime/src/ebpf_control.rs:103-122) plans the guard ONLY when a confine
target PID is explicitly configured AND the policy lowers to a non-empty syscall
allowlist — deliberately, since loading it with an empty allowlist would SIGKILL
the confined process on its next syscall. A reviewer reading a kill-switch could
otherwise credit a deployment with post-escape containment it never enabled.

**Proxy egress allowlist — empty by default, i.e. default-open.**
aa-proxy/src/config.rs:79-92: "When empty (the default), no allowlist filter is
applied"; denied_hosts is empty too, and in_tunnel_deny_reason_empty_allowlist_is_
default_open pins the behaviour. Stated in both the Boundary and Policy rows,
paired with the control that IS always on: the SSRF guard, which config.rs:183
confirms production binaries can never relax.

**eBPF collection scope — a false completeness claim, and a privacy one.**
"Scope is limited to these probes" omitted fourteen file-I/O kprobe targets
(aa-ebpf/src/kprobe.rs:145-160) whose payload carries a 256-byte path field
(aa-ebpf-common/src/file.rs:88) — full file paths — plus process-exit events. TLS,
file-I/O and exec all load unconditionally. A DPIA reviewer would have mis-scoped
their assessment.

Also corrects three verified imprecisions: the kernel floor is 5.8+ with BTF, not
5.3 (that is only the bpf_send_signal helper floor); LibreSSL is NOT blind, since
discovery is a substring match on libssl.so and LibreSSL exports SSL_read/SSL_write
— Node/BoringSSL is blind because static linking leaves no libssl.so mapping; and
aa-api never logs query strings at all, so the redaction sentence named the wrong
verb and the wrong component.

Cells edited in place — table shape unchanged.

Refs: AAASM-5612
…stency

SSO/SAML/OIDC, SCIM and the Vault layer carry a 🗺️ marker; the AES-256-GCM
retraction relied on bold prose alone, so a reader scanning for status emoji saw
no marker on the one term whose correction matters most.

Refs: AAASM-5612
…ne unreachable audit stream

The fourth question — does the named mechanism exist, and can a released binary
reach it — applied to the proxy row, which had survived a vocabulary scan, a
read-as-guarantee pass and a defaults pass because it reads as competent
description.

**"Connection pool limits per agent; circuit breaker on upstream failure."**
Neither exists. Over aa-proxy/src, case-insensitive: pool 0, circuit 0, breaker 0,
semaphore 0, concurrency_limit 0 — against CertCache 11 from the same command, so
the search reached the files. No tower dependency. The accept loop is an
unconditional tokio::spawn per connection with no permit, counter or cap. Replaced
with the true statement: nothing bounds connection concurrency.

**"The proxy writes its own JSONL audit record per intercepted request."**
Unreachable in any shipped build: ProxyServer::new hardcodes the sink to None,
the emit path early-returns on None, ProxyConfig has no audit-path field, and the
binary path (aa-proxy/src/lib.rs:70) uses ProxyServer::new. Every caller passing
Some(...) is a test. An auditor told this file exists would go looking for
something no deployment produces.

Also bounds the Elevation-of-Privilege cell in the same row: harden_process is
PR_SET_DUMPABLE=0 on Linux, best-effort and non-fatal, which blocks same-uid
ptrace and core dumps. It does not make the proxy run unprivileged or restrict
filesystem writes — those are deployment properties the software does not enforce.

Refs: AAASM-5612
…rrected into a false weakness

The page described agent-plane auth three inconsistent ways and one of them was
false in the dangerous direction: "Anyone who can reach the port can call policy
evaluation and agent registration unauthenticated." A public page saying anyone
can register agents unauthenticated is untrue and invites a needless incident
report.

The interceptor split I documented was right; what none of the three statements
accounted for is that the handlers self-validate. Re-derived:

- register (lifecycle_service.rs:396) calls enforce_did_key_binding (:232-239),
  which returns Status::unauthenticated unless the agent_id's did:key encodes the
  supplied public key — so an agent can only register as an identity whose key it
  holds. It then consumes a single-use identity-bound nonce (:454) and verifies an
  Ed25519 possession proof (:456).
- heartbeat / deregister / control_stream validate the token.
- CheckAction's validate_credential_token (policy_service.rs:1110-1128) rejects an
  empty or mismatched token for a registered agent, constant-time.

The real gap is much narrower: an UNREGISTERED caller with an EMPTY token gets a
policy evaluation, with tenancy neutralised to anonymous by
apply_authoritative_tenancy (:398-412) rather than taken from the request.

All three places now say the same accurate thing, and the auth-flow callout gains
a dedicated block since the nuance does not fit a table cell. Earlier rounds of
this ticket over-stated; this one had over-corrected, which is its own defect.

Refs: AAASM-5612
…rites one fixed file

Stated in three places: the Telemetry layer row, the Audit log section, and the
glossary. All three are wrong for the only path that ships.

audit_file_path (aa-gateway/src/server.rs:74-76) formats
`{agent_id}-{session_id}.jsonl`, but both shipped serve paths call
setup_audit("gateway", "default", storage) (:627, :787) — so a real gateway
produces a single gateway-default.jsonl that grows without bound. The per-session
capability exists and is never exercised in production.

This one has operational consequences rather than just being inaccurate: an
operator planning retention, rotation or per-tenant separation around per-session
files gets none of it, and finds out from disk pressure. Called out explicitly for
that reason.

Refs: AAASM-5612
…t defaults

**Schema validation** — effect partially holds, mechanism was wrong. aa-api has no
JSON-Schema or OpenAPI validation middleware: jsonschema 0, openapiv3 0,
validate_request 0, schema_validat 0, against serde 487 and utoipa 182 from the
same command. What rejects malformed input is serde deserialisation into typed
extractors; utoipa generates the spec and CI checks drift. Schema-only constraints
hold only where a handler hand-wrote a check — a reader relying on `pattern` or
`minLength` being enforced at the edge would be wrong.

**Budget caps** — the same off-by-default shape as the egress allowlist two cells
away, which is why it is worth stating twice. `budget` is Option, the tracker
starts with no team limit, and enforcement is limit-gated, so an undeclared budget
means uncapped spend. policy-examples/audit-only.yaml says it outright — "No
`budget:` clause → no spend cap is enforced" — and no shipped example sets a team
limit. Also drops the conflation of budget with rate limiting in the SDK row:
budgets cap spend, not request rate.

Refs: AAASM-5612
…m a released build

Three findings from running "does it exist, and can a released binary reach it"
over the cells nobody had flagged.

**The eBPF layer is unreachable from a release.** aa-ebpf-loaderd is explicitly
listed in UNRELEASED_BINARIES (scripts/check-release-completeness.sh) — "not part
of the release artifact set" — and release.yml builds only aa-cli, aa-gateway,
aa-runtime, aa-api and aa-proxy (Linux). Without the daemon socket the runtime
silently degrades to SDK-only rather than erroring, so an operator can believe the
kernel layer is active when nothing was ever loaded. The page said "Linux 5.8+ with
BTF" and "loaded only by a privileged system service" and never that the daemon
ships from source only. Stating the fact; the packaging decision is a product
ticket, not mine.

**SDK integrity** was credited to this product. Cargo/npm/PyPI checksums protect
the download and are the registry's control; nothing here verifies the SDK at
runtime.

**Credential scanner scope** — on by default, but in RedactOnly mode (redact and
forward, not block), and under the llm_only default only built-in LLM hosts are
intercepted, so only those are scanned.

For the record on the same sweep: the Slack/webhook alerting connectors in the
Telemetry row DO exist (aa-api/src/destinations/connectors/), so that cell stands
unchanged.

Refs: AAASM-5612
…d name the fourth question

The Pagefind negative scan caught a survivor the page edits missed: the checklist's
tamper-evidence row still described "the per-session JSONL files", contradicting
security-model and glossary one commit after both were corrected. The register is
the artifact a future maintainer consults, so a stale mechanism description there
outlives the page text.

Also adds the reachability rule as its own language rule. It is distinct from the
three already listed — correct wording, holds-once-enabled, and on-by-default — and
it is the one that found the proxy audit sink, the loaderd exclusion and the
fixed-filename gateway path. All three passed every earlier check.

Refs: AAASM-5612
…opped, and it is not CAP_BPF only

"Loaded via CAP_BPF only; capability is dropped after program load." Both halves
are false.

Exhaustive search of aa-ebpf/src against a positive control of `Ebpf` = 356 hits:
drop_capabilit 0, caps:: 0, capctl 0, prctl 0, PR_CAPBSET 0, setresuid 0,
cap_drop 0, SECBIT 0. The only capability dependency is libc — no capability
crate. The sole mentions are systemd-unit doc comments in bin/loaderd.rs:26-27,
which BOUND the set at launch rather than dropping anything afterwards, and they
grant CAP_BPF *and* CAP_PERFMON, so "CAP_BPF only" is wrong too.

It also cannot be true by construction: control/mod.rs:5-7 describes a
long-running daemon that "owns every aya::Ebpf handle and holds the only CAP_BPF /
CAP_PERFMON", servicing further LoadProbeSet and UpdateSyscallAllowlist requests
over its control socket. Dropping the capability after the first load would break
the daemon's purpose.

The replacement keeps the privilege separation that genuinely holds rather than
just deleting the claim — bounding set limited to two capabilities,
NoNewPrivileges, ProtectSystem=strict, sole ownership of the Ebpf handles, and a
root-owned 0600 socket across which no raw fd or handle passes.

Where it hid: the Spoofing and Info-Disclosure cells in this same row were both
rewritten last round while this cell carried its original text through untouched.
A row is not swept because some of its cells were.

Refs: AAASM-5612
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@Chisanan232

Copy link
Copy Markdown
Contributor Author

Claude Code review complete — APPROVE-READY

Five independent review rounds. Recording the verdict here because GitHub does not permit the author account to approve its own pull request.

Requirements. All acceptance criteria met. No public page presents unavailable SaaS functions or commitments in the present tense; Coming soon pages carry no fake-operational instructions or contractual promises; compliance and legal language is evidenced or removed; planned content is separated from current capability; docs build, links, metadata and claim scans pass.

Scope grew three times during the ticket, each time deliberately. It began as the three SaaS pages, then took security-model.md (which published compliance and audit claims of exactly the class being removed), then self-host-observability.md (a regression this PR itself introduced — the SLA figures it deleted survived only in a cross-reference), then glossary.md (five stale definitions, not the one identified). Eight files in total.

Claim accuracy — both directions. 32 unverified SaaS operational and contractual claims were removed rather than softened: regions, tiers, quotas, SLAs with service credits, support response times, billing and procurement, DPA/BAA, SOC 2 / HIPAA / GDPR, SSO and SCIM walkthroughs. The compliance status table was removed rather than relabelled — there was no artifact to attach a Planned label to, and relabelling would have left the softer promise the ticket exists to eliminate.
Equally, five understatements were found and corrected, four of them introduced by fixes for overstatements: aasm audit verify-chain genuinely ships; ExportFormat has three variants with --format required; an InMemorySecretsStore is mounted (the accurate statement is narrower and more damning than "no secret store ships"); LibreSSL is hooked, not blind; and the syscall guard genuinely enforces, with a documented kill-after-syscall race.

Four dead or nonexistent mechanisms found and stated as such: the proxy's per-request JSONL audit sink (hardcoded None; every Some(...) caller is a test), connection pool limits and a circuit breaker (neither exists — pool/circuit/breaker/semaphore/concurrency_limit all 0 against a CertCache control of 11), OpenAPI runtime schema validation (serde into typed extractors; no validation middleware), and the eBPF layer itself (aa-ebpf-loaderd is excluded from the release artifact set, so a released build silently degrades to SDK-only). Filed as AAASM-5640 and AAASM-5641.

One correction went the other way and mattered most. An earlier revision stated that anyone reachable on the port could call agent registration unauthenticated. That is false: Register enforces a did:key binding, consumes a single-use identity-bound nonce, and verifies an Ed25519 possession proof. The real gap is narrower — an unregistered caller with an empty token receives a policy evaluation, tenancy neutralised to anonymous. Publishing the broader claim would have invited an incident report about a vulnerability that does not exist.

Security. Compliance and legal claims removed with a publication checklist naming, per claim, the owner and the concrete evidence required to restore it — three rows marked "contradicted by the source", meaning restoration needs a code change rather than an approval. No secrets. cloud and saas-infra are private and no private content appears in the diff.

Tests/CI. All 38 commits build; mdBook green in both languages, three drift checks exit 0, lychee 108 OK / 0 errors on the touched pages. The 10 whole-repo lychee errors were verified byte-identical at merge-base in files this PR does not touch. Pagefind: 44 pages, both languages, 31 retired phrases absent and 8 replacement markers confirmed present — an absence-only scan cannot distinguish "fixed" from "page missing". CI 3 passed / 0 failed, zero unresolved threads, no merge commits, no force-push, no --no-verify.

Sonar/coverage: SonarCloud green; no coverage gate configured for this repository.

Known limitations. Residuals stand at four claims across three files, recorded in the PR body. docs #135 merged first, resolving the HMAC contradiction that made merge ordering a hard condition.

@Chisanan232
Chisanan232 merged commit e2a5286 into main Aug 6, 2026
4 checks passed
@Chisanan232
Chisanan232 deleted the v0.0.1-rc.7/AAASM-5612/remove_unverified_saas_claims branch August 6, 2026 04:40
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