@@ -177,6 +177,34 @@ catalog (summary strip + URL-synced filters + keyset infinite scroll + detail pa
177177audit history) plus a create/replace dialog that never echoes values and a security
178178posture card that warns when ` SECRETS_MASTER_KEY ` is unset.
179179
180+ ** Detected requirements (sync-time reference discovery).** Workflow parsing records what
181+ each workflow * expects* as structured metadata in ` workflows.metadata ` JSONB — no
182+ migration: ` secretRefs ` (` ${{ secrets.NAME }} ` , dot AND bracket ` secrets['NAME'] ` forms,
183+ word-boundary checked so ` mysecrets. ` never matches, ` GITHUB_TOKEN ` excluded),
184+ ` varRefs ` (` ${{ vars.NAME }} ` — detect/display only; the platform doesn't manage plain
185+ variables), and ` environments ` (distinct job ` environment: ` names, case-insensitively
186+ deduped, slug-filtered so dynamic ` ${{ … }} ` names are never stored). All scanners share
187+ ` workflow_parse::scan_context_refs ` (identifier ` [A-Za-z_][A-Za-z0-9_]* ` ≤200 B, dedupe,
188+ cap 100, sort). Two read endpoints compute "referenced but not configured" as a set
189+ difference at QUERY time (`db/workflows.rs::missing_secret_refs/workspace_var_refs/
190+ missing_environment_refs` — ` jsonb_array_elements_text` LATERAL with a ` jsonb_typeof`
191+ guard so pre-feature/failed-parse metadata reads as empty; anti-join semantics: a secret
192+ name counts as configured via workspace scope, same-repo repository scope, or any
193+ environment scope): ` GET …/secrets/requirements ` (` secrets.read ` ) and
194+ ` GET …/environments/requirements ` (` content.read ` ). Handlers re-filter names at read
195+ time against the configurable-name allow-lists (defense-in-depth against hand-edited
196+ metadata) and cap output (200 names, 20 refs/name, true ` referenceCount ` ). YAML never
197+ mints rows — the UI offers one-click create through the ordinary RBAC'd endpoints:
198+ ` DetectedRequirementsCard ` (secrets page right rail; "Add" pre-fills ` SecretFormDialog `
199+ via ` presetName ` /` presetRepository ` , repo scope only when every reference shares one
200+ repo) and ` DetectedEnvironmentsCard ` (environments page; "Create" pre-fills
201+ ` EnvironmentFormDialog ` via ` presetName ` ). Both requirements queries live under their
202+ feature's react-query prefix, so every mutation's existing invalidation clears satisfied
203+ warnings automatically. The environment detail page lists ` boundWorkflows ` (from
204+ ` list_binding_environment ` , case-insensitive) linking to the workflow detail pages.
205+ Detection refreshes on every repo sync; workflows synced before this feature light up
206+ after their next push/manual re-sync.
207+
180208** Notification Center (operational inbox).** Notifications are a per-user, actionable
181209PROJECTION of the immutable ` audit_logs ` ledger — the Activity Feed keeps the complete
182210history, notifications hold only what a user should act on (OWASP's audit-vs-messaging
@@ -265,14 +293,18 @@ overup/
265293│ │ ├── pipelines/ # execution ledger + live detail workspace: PipelineGraph,
266294│ │ │ # ExecutionTimeline, LogViewer (xterm), tab panels,
267295│ │ │ # usePipelineStream (WS), stores/logStore (zustand)
268- │ │ ├── artifacts/ # workspace artifact catalog: summary strip, URL-synced
269- │ │ │ # filters, keyset infinite scroll, provenance detail page
296+ │ │ ├── artifacts/ # workspace artifact catalog: summary strip (4 KPI cells —
297+ │ │ │ # the house norm), URL-synced filters, keyset infinite
298+ │ │ │ # scroll, provenance detail page
270299│ │ ├── secrets/ # write-only encrypted secrets: catalog + posture column,
271- │ │ │ # create/replace dialog (value never echoed), detail
272- │ │ │ # page with audit history
300+ │ │ │ # detected-requirements card (missing secretRefs +
301+ │ │ │ # vars, one-click Add), create/replace dialog (value
302+ │ │ │ # never echoed), detail page with audit history
273303│ │ ├── environments/ # deployment environments: catalog (summary strip +
274- │ │ │ # URL-synced search + keyset infinite scroll), detail
275- │ │ │ # page with scoped secrets + audit, create/edit dialog
304+ │ │ │ # URL-synced search + keyset infinite scroll +
305+ │ │ │ # detected-environments card w/ one-click create),
306+ │ │ │ # detail page with scoped secrets + bound workflows +
307+ │ │ │ # audit, create/edit dialog
276308│ │ ├── notifications/ # operational inbox: NotificationBell (badge, popover/
277309│ │ │ # bottom-sheet switch), NotificationPanel, history page,
278310│ │ │ # PreferencesDialog, useNotificationStream (per-user WS)
@@ -558,6 +590,13 @@ into a traversal-safe tar streamed into the container via the Docker archive API
558590 ` environment.created/updated/deleted ` audit rows in-transaction, and a delete cascade
559591 audits every removed secret. A secret create with an ` environmentId ` from another
560592 workspace gets the same flat error as a bad ` repositoryId ` — no existence oracle
593+ - ** Detected requirements are read-only projections of parser output** : sync-time
594+ scanners only store allow-listed identifiers (charset + length + caps) into
595+ ` workflows.metadata ` ; the requirements endpoints re-filter names at read time against
596+ the same configurable-name rules, cap output (200 names / 20 refs each), ride the
597+ sibling read permissions (` secrets.read ` / ` content.read ` ), and expose names only —
598+ never values. YAML never mints secrets or environments: "Add"/"Create" go through the
599+ ordinary RBAC'd mutation endpoints
561600- ** Notifications are self-scoped by construction** : every read/mutation predicate pins
562601 ` user_id = caller ` in SQL (a forged notification id 404s flat), the live hub is keyed by
563602 the AUTHENTICATED user id (routing, not filtering, is the isolation), titles/bodies are
0 commit comments