# Data flow — garrytan/gstack

> Canonical page: https://codevetter.com/unpack/gstack/data-flow

# Data flow — garrytan/gstack

Scanned commit 35dd014c580037d1c009776d8b0584587c1bd42c (2026-09-21). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

There are four main data pipelines. (1) Doc generation: .tmpl + resolvers + host config → committed SKILL.md per host. (2) Browser commands: CLI → state file → localhost POST /command → scoped-token check → Playwright/CDP → sanitizers + untrusted-content envelope → stdout. (3) Rendering: a local HTML file is served on a loopback Bun.serve port and printed/screenshotted through Aside or the browse daemon. (4) Telemetry/egress: every off-machine send writes a hash-chained receipt before transmitting; eval results land under ~/.gstack/projects/<slug>/evals/. State ownership is split between per-project .gstack/ (daemon state, logs) and the machine-level state root from bin/gstack-paths (sessions, egress ledger, security logs, evals).
- Skill generation pipeline: SKILL.md.tmpl → scripts/gen-skill-docs.ts finds {{PLACEHOLDER}} tokens → RESOLVERS functions produce text → per-host frontmatter transform (allowlist/denylist, descriptionLimit, conditionalFields), pathRewrites and toolRewrites → writes SKILL.md (claude) or <hostSubdir>/skills output (other hosts); --dry-run powers CI freshness checks. — [scripts/gen-skill-docs.ts#L1-25](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/gen-skill-docs.ts#L1-L25) [scripts/resolvers/index.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/resolvers/index.ts) [scripts/host-config.ts#L43-79](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/host-config.ts#L43-L79)
- gen-skill-docs also emits two non-skill generated artifacts: review/design-checklist.md (from lib/design-catalog.ts via scripts/resolvers/design-checklist.ts) and lib/dom-dump.js (from lib/dom-dump-script.ts); test/design-checklist-sync.test.ts fails on drift. — [scripts/gen-skill-docs.ts#L13-15](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/gen-skill-docs.ts#L13-L15) [ARCHITECTURE.md#L355](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L355) [lib/design-catalog.ts#L8-16](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/lib/design-catalog.ts#L8-L16)
- A browse command flows: cli.ts readState() → isServerHealthy() GET /health → spawn server.ts if absent → POST /command with Authorization: Bearer <token> → server validates token (root or scoped via token-registry checkScope/checkDomain/checkRate) → handleReadCommand/handleWriteCommand/handleMetaCommand → Playwright → result. — [browse/src/cli.ts#L141-160](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/cli.ts#L141-L160) [browse/src/server.ts#L16-55](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/server.ts#L16-L55) [browse/src/token-registry.ts#L1-32](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/token-registry.ts#L1-L32)
- Page-derived output is wrapped as untrusted: PAGE_CONTENT_COMMANDS results pass through wrapUntrustedContent (BEGIN/END UNTRUSTED EXTERNAL CONTENT markers with marker-escape and URL sanitization), and DOM_CONTENT_COMMANDS additionally run markHiddenElements content filters in browse/src/content-security.ts before the read. — [browse/src/commands.ts#L53-89](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/commands.ts#L53-L89) [browse/src/content-security.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/content-security.ts) [browse/src/server.ts#L22-26](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/server.ts#L22-L26)
- Every server egress of page-content strings routes through Unicode lone-surrogate sanitization: handleCommandInternal wraps the result string, and the SSE producers (/activity/stream, /inspector/events) stringify with sanitizeReplacer — an invariant pinned by browse/test/server-sanitize-surrogates.test.ts. — [browse/src/server.ts#L78-94](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/server.ts#L78-L94) [browse/src/sanitize.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/sanitize.ts) [ARCHITECTURE.md#L204-217](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L204-L217)
- Browser event data (console, network, dialog) accumulates in three 50K-entry in-memory CircularBuffers flushed asynchronously to .gstack/*.log every ~1s; the console/network/dialog commands read memory, disk is for post-mortem. — [ARCHITECTURE.md#L289-304](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L289-L304) [browse/src/buffers.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/buffers.ts)
- The render pipeline: bin/gstack-render.ts parses argv into a RenderSpec → lib/aside-render.ts pickEngine() → probeAside() (aside --version + aside repl ASIDE_READY probe) → serve the file's directory on 127.0.0.1 with a per-render secret path segment → one aside repl script emits GSTACK_RENDER_OK + ASIDE_DIR → artifacts copied out; on the fallback path the same spec is executed as newtab/goto/pdf --from-file/screenshot/closetab daemon calls. — [bin/gstack-render.ts#L42-60](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/bin/gstack-render.ts#L42-L60) [lib/aside-render.ts#L69-93](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/lib/aside-render.ts#L69-L93) [ARCHITECTURE.md#L104-115](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L104-L115)
- Off-machine sends are receipted before transmission: lib/egress-receipt.ts writeReceipt appends a content-free (sha256+bytes, never payload), hash-chained line to ~/.gstack/security/egress.jsonl (0600); shell sinks use _receipted_curl/_receipted_git from bin/gstack-egress-lib.sh; failure polarity is fail-closed for sensitive sinks and fail-open for user-facing ones. — [lib/egress-receipt.ts#L1-25](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/lib/egress-receipt.ts#L1-L25) [bin/gstack-egress-lib.sh](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/bin/gstack-egress-lib.sh) [ARCHITECTURE.md#L196-202](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L196-L202)
- Every skill invocation begins by running bin/gstack-skill-start (the consolidated preamble runtime that replaced ~18KB of inline bash): it performs update check, session tracking under ~/.gstack/sessions/, contributor mode, and emits KEY: value STATUS lines the skill prose branches on; bin/gstack-skill-end logs telemetry at completion. — [ARCHITECTURE.md#L357-362](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L357-L362) [bin/gstack-skill-start](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/bin/gstack-skill-start) [SKILL.md.tmpl#L21](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/SKILL.md.tmpl#L21)
- Paid-eval selection is data-driven: test/helpers/touchfiles.ts re-exports literal-only maps (E2E_TOUCHFILES, E2E_TIERS, LLM_JUDGE_TOUCHFILES, GLOBAL_TOUCHFILES) from touchfiles-data.ts; test-selection.ts computes selectTests() from `git diff` against the detected base branch; results persist via test/helpers/eval-store.ts to ~/.gstack/projects/<slug>/evals/. — [test/helpers/touchfiles.ts#L1-43](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/test/helpers/touchfiles.ts#L1-L43) [test/helpers/eval-store.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/test/helpers/eval-store.ts) [docs/TESTING_INTERNALS.md#L61-65](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/docs/TESTING_INTERNALS.md#L61-L65)
- Multi-agent browser access uses scoped tokens: root token → POST /token or POST /connect setup-key exchange → scoped tokens carrying subsets of read/write/admin/meta/control scopes derived-but-reclassified from commands.ts (js/eval/cookies land in admin, not read); control is never granted by default. — [browse/src/token-registry.ts#L14-32](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/token-registry.ts#L14-L32) [browse/src/token-registry.ts#L44-98](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/token-registry.ts#L44-L98)
- Cookie import reads the real browser's Chromium SQLite cookie DB via a copied temp file opened read-only, decrypts values in-process (PBKDF2 + AES-128-CBC after a user-approved macOS Keychain prompt), loads them into the Playwright context, and never writes plaintext to disk or logs. — [ARCHITECTURE.md#L180-190](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L180-L190) [browse/src/cookie-import-browser.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/browse/src/cookie-import-browser.ts)
- Model selection is orthogonal to host: scripts/models.ts ALL_MODEL_NAMES + resolveModel() normalize CLI input to a family; the generator renders model-overlays/<family>.md per host using HostConfig.defaultModel unless --model is explicit; setup reads Codex's config.toml via scripts/resolve-codex-generation-model.ts. — [scripts/models.ts#L1-30](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/models.ts#L1-L30) [model-overlays/claude.md](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/model-overlays/claude.md) [scripts/resolve-codex-generation-model.ts](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/resolve-codex-generation-model.ts)
- The Aside research path funnels through a generated `_aside_exec` bash wrapper ({{ASIDE_EXEC_PRELUDE}}) that writes an egress receipt before the prompt leaves the machine — skills are forbidden from calling `aside exec` bare. — [scripts/resolvers/aside.ts#L108-120](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/scripts/resolvers/aside.ts#L108-L120) [ARCHITECTURE.md#L25](https://github.com/garrytan/gstack/blob/35dd014c580037d1c009776d8b0584587c1bd42c/ARCHITECTURE.md#L25)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
