feat: demo SaaS + training flow security hardening (8/8 review gate passed)
- Demo accounts: super_admin-only provisioning into isolated DEMO_ORG_ID tenant, 30-day UTC trial on first login, revocable, one-time credential delivery via optional SES/webhook (never persisted). Adds boto3 dependency. - Analytics/report/export/privacy: shared bounded scan budget across users/groups/ sessions, tenant-consistent session/user/group joins, scalar-only CSV export (no nested persisted-value stringification). - Ownership/tenant isolation: canonical owner-tenant predicate for list/read/chat; client sees is_owned only, never owner_user_id. - Lifecycle/races: status transition validation, analyzing is an in-progress gate (no duplicate reanalysis), structured-ready publication, stale-variant revalidation. - Auth/setup/consent/JWT/OAuth/config: fail-closed consent, bounded JWT lifetime, provider-subject atomic OAuth identity, repeated-secret rejection, strict Persona trait validation. - Chat/session/privacy: pre-seller opener redaction, corrupt-session recovery, role-aware completed-chat dashboard routing. - Frontend: Training→product→personas→practice flow, demo/role/demo guards, is_owned-based ownership display, 320×568 and 500×768 responsive E2E. - 8 independent exact-five-key review scopes passed; backend 509, frontend 26, production build 1775 modules, isolated E2E 15.
This commit is contained in:
@@ -23,7 +23,49 @@ filesystem JSON storage (no SQL). i18n TH/EN. No self-registration (admin provis
|
||||
- **admin** — manages groups/users, sees personas with **secret fields stripped** (IP protection).
|
||||
- **user** (trainee) — trains against personas, own board.
|
||||
|
||||
## 2026-08-25 review checkpoint — exact-current local code gate passed 8/8
|
||||
> All eight independent review scopes now have complete five-key passes with
|
||||
> empty security and logic arrays: ownership/tenant isolation; lifecycle/races;
|
||||
> auth/setup/consent/JWT/OAuth/config; Chat/session/privacy;
|
||||
> analytics/report/export/privacy; frontend Training-to-Practice; frontend-backend
|
||||
> API contracts; and final cross-cutting security/correctness.
|
||||
>
|
||||
> Wave 3 required two remediation rounds. The first bounded analytics scans,
|
||||
> enforced tenant-consistent session/user/group joins, disabled analysis actions
|
||||
> while `analyzing`, and routed completed regular-user chats to `/my/board`. The
|
||||
> second replaced CSV stringification of arbitrary nested persisted values with a
|
||||
> bounded scalar-only contract. Fresh analytics review `deleg_bcb1510f` and
|
||||
> frontend review `deleg_1fed2524` both passed.
|
||||
>
|
||||
> Final Wave 4 `deleg_97eb385c` passed API contracts and cross-cutting review.
|
||||
> Latest evidence: backend **509 passed in 73.27s**; frontend **26/26**; production
|
||||
> build **1775 modules**; isolated Playwright **15/15** across desktop, 320×568,
|
||||
> and 500×768; compileall, source security scan, and `git diff --check` passed.
|
||||
>
|
||||
> Non-blocking follow-ups: parameterize `_csv_cell` edge cases, add malformed API
|
||||
> envelope tests for view consumers, and prevent Playwright from reusing an
|
||||
> unrelated server on port 3000. No live OAuth provider or production runtime was
|
||||
> verified. Branch is `main`; HEAD is
|
||||
> `8a632b5e6a3b67c9acd2787e1a686ac17ae69486`; index is empty. No stage, commit,
|
||||
> push, deploy, reset, or stash occurred; broad user WIP remains untouched.
|
||||
|
||||
## Current state — local code/security gate passed; production-operation gate pending
|
||||
> **2026-08-23 — Demo SaaS:** Implemented and locally verified; not deployed or pushed. Demo accounts use
|
||||
> role `demo` in the dedicated `DEMO_ORG_ID` tenant. Only `POST /api/admin/demo-accounts` (super_admin
|
||||
> only) can provision them; generic user creation and other role paths are blocked. The first successful
|
||||
> password login starts exactly one fixed **30-day UTC trial**; the window is idempotent. A super_admin
|
||||
> can revoke via `POST /api/admin/demo-accounts/<username>/revoke` (DELETE alias), which invalidates
|
||||
> existing tokens. Demo users can access only demo-visibility, `ready` groups in the demo tenant;
|
||||
> backend mutation guards block demo group/persona creation and visibility changes. Creation returns the
|
||||
> username and temporary password once plus SES/webhook delivery status; the password is never
|
||||
> retrievable later or persisted in stores, audit logs, delivery events, or application logs. Configure
|
||||
> `DEMO_ORG_ID` (optionally `DEMO_ORG_NAME`/`DEMO_ORG_SEATS`), the AWS credential chain + `AWS_REGION`
|
||||
> + verified `SES_FROM_EMAIL` (`SES_REPLY_TO` optional), and HTTPS `DEMO_WEBHOOK_URL` +
|
||||
> `DEMO_WEBHOOK_SECRET`. Focused demo tests: **10 passed**; full backend: **358 passed**; frontend
|
||||
> Vitest: **5 passed**; `npm run build`: **passed**. Remaining operational gates are to configure and
|
||||
> verify the SES sender/AWS credentials and webhook secret, test in a restricted environment, and get
|
||||
> operator approval. No live SES/webhook delivery, production operation, deploy, or push has been
|
||||
> performed.
|
||||
> **2026-08-21:** UX/SAAS 12-point redesign (**PUSHED + LIVE, verified 2026-08-21**). Self-registration
|
||||
> (`POST /api/auth/register`, role=user, first-created-user = super_admin), super_admin may now
|
||||
> promote others + is invisible to regular admin, user-created private product groups + admin
|
||||
|
||||
@@ -32,14 +32,17 @@ Informed by MiroFish (CrowdSight engine) + the hermes-brain-and-tools CrowdSight
|
||||
| S4.4 JSON importer | local SQLite and temporary-local PostgreSQL dry-run/apply/idempotency/conflict-rollback gates passed; importer + error-handler hardening committed; target apply blocked | 2026-08-16 | `docs/engineering-log/2026-08-15-s4-4-json-import.md`, `docs/engineering-log/2026-08-15-postgresql-import-gate.md`, `docs/engineering-log/2026-08-16-s4-4-importer-errorhandler-commit.md`, `docs/test-evidence/2026-08-15-postgresql-import.md` | target snapshot checksum/count comparison, retained backup, and operator-approved rollback rehearsal |
|
||||
| UX/UI redesign + marketing website | implemented + locally verified (build clean, 4/4 unit tests, independent review PASS, responsive verified); uncommitted; deploy pending operator approval | 2026-08-19 | `docs/engineering-log/2026-08-19-ux-redesign-and-marketing-site.md`, `website/`, `git diff` | operator approves push (auto-deploys); confirm production `JWT_SECRET` before deploy |
|
||||
| OAuth Google + Facebook login/register | implemented + locally verified (348 backend tests incl. 11 new, frontend build + 4/4 unit, manual security review PASS, no new deps); uncommitted | 2026-08-20 | `docs/engineering-log/2026-08-20-oauth-google-facebook.md`, `backend/app/services/oauth.py`, `backend/app/api/oauth_routes.py`, `backend/tests/test_oauth.py` | operator approves push; set OAUTH_* creds + OAUTH_DEFAULT_ORG in EasyPanel env to enable (disabled by default, fail-closed) |
|
||||
| Training product-to-persona flow + security remediation | exact-current local code/security gate passed 8/8 independent scopes; production-operation gate pending | 2026-08-25 | `docs/engineering-log/2026-08-24-training-security-review.md`, `docs/test-evidence/2026-08-24-training-security-review.md` | operator decides whether to stage/commit/push; live OAuth or production verification remains a separate approval gate |
|
||||
|
||||
## Guardrails
|
||||
- No self-registration; admin provisions users. (Verified: register => 404.)
|
||||
- Public self-registration may create only role `user`; super-admin bootstrap is
|
||||
factory-controlled and cannot be re-armed by deleting users.
|
||||
- One persona = one chat per user (one-shot). Enforced in SessionStore + chat start.
|
||||
- Latent persona fields never leak to trainees pre-result.
|
||||
- LLM credentials live in `.env` only; never logged.
|
||||
|
||||
## Entry index
|
||||
- `2026-08-24-training-security-review.md` — current Training flow/security remediation evidence, review timeout handling, and blocked stage/commit/push gate.
|
||||
- `2026-08-07-build-out.md` — M0–M7 build-out, decisions, verification, current state.
|
||||
- `2026-08-07-security-ux.md` — security hardening (path traversal, IDOR, XSS) + UX/UI polish.
|
||||
- `2026-08-07-auth-gitea.md` — username login + first-time admin setup + Gitea push.
|
||||
|
||||
67
docs/engineering-log/2026-08-23-demo-saas.md
Normal file
67
docs/engineering-log/2026-08-23-demo-saas.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# 2026-08-23 — Demo SaaS account provisioning and restricted trial
|
||||
|
||||
Date: 2026-08-23
|
||||
Status: implemented + locally verified; live delivery, production operation, deployment, and push not performed
|
||||
|
||||
## Scope
|
||||
|
||||
- Added the `demo` role with a dedicated `DEMO_ORG_ID` tenant. Demo accounts can be created only
|
||||
through `POST /api/admin/demo-accounts` by a `super_admin`; generic user creation and direct
|
||||
`role=demo` mutations are blocked.
|
||||
- A demo account starts exactly one fixed **30-day UTC trial** on its first successful password
|
||||
login. The trial timestamps are record-locked and idempotent. `super_admin` can revoke through
|
||||
`POST /api/admin/demo-accounts/<username>/revoke` (with the DELETE alias), deactivating the
|
||||
account and invalidating existing tokens.
|
||||
- Demo users can access only demo-visibility, `ready` groups in the demo tenant. Backend guards
|
||||
block cross-tenant access, group/persona creation, and visibility changes for the `demo` role.
|
||||
- Provisioning returns the generated username and temporary password once, together with SES and
|
||||
authenticated HTTPS webhook delivery status. Delivery is opt-in/best-effort; the temporary
|
||||
password is not retrievable later and is not persisted in user stores, audit logs, delivery
|
||||
events, or application logs.
|
||||
|
||||
## Implementation surface
|
||||
|
||||
These are the implementation modules documented by this entry; this documentation update did not
|
||||
modify code or tests.
|
||||
|
||||
- Backend configuration/wiring: `backend/.env.example`, `backend/app/config.py`,
|
||||
`backend/app/factory.py`, `backend/requirements.txt`, `backend/requirements.lock.txt`.
|
||||
- Identity and API policy: `backend/app/auth/users.py`, `backend/app/api/auth_routes.py`,
|
||||
`backend/app/api/admin_routes.py`, `backend/app/api/group_routes.py`,
|
||||
`backend/app/api/chat_routes.py`, `backend/app/api/me_routes.py`, and
|
||||
`backend/app/api/helpers.py`.
|
||||
- Delivery and storage: `backend/app/services/demo_delivery.py` and
|
||||
`backend/app/services/groups.py`.
|
||||
- Frontend wiring/UI: `frontend/src/api/index.js`, `frontend/src/i18n/index.js`,
|
||||
`frontend/src/store/auth.js`, `frontend/src/router.spec.js`, and the affected views
|
||||
`AdminUsers.vue`, `GroupBuilder.vue`, `GroupEdit.vue`, `MyBoard.vue`, `Personas.vue`,
|
||||
`SessionDetail.vue`, and `Training.vue`.
|
||||
- Focused regression coverage: `backend/tests/test_demo_accounts.py`.
|
||||
|
||||
## Verification evidence
|
||||
|
||||
| Command | Result |
|
||||
|---|---|
|
||||
| `cd backend && uv run pytest -q tests/test_demo_accounts.py` | **10 passed** |
|
||||
| `cd backend && uv run pytest -q` | **358 passed** |
|
||||
| `cd frontend && npm run test:unit` (Vitest) | **5 passed** |
|
||||
| `cd frontend && npm run build` | **passed** |
|
||||
|
||||
## Limitations and remaining operational gates
|
||||
|
||||
- SES delivery was not exercised against AWS. Before operational use, configure a verified
|
||||
`SES_FROM_EMAIL`, `AWS_REGION`, and the normal AWS credential chain (`SES_REPLY_TO` is optional).
|
||||
- Webhook delivery was not exercised against a live receiver. Configure HTTPS `DEMO_WEBHOOK_URL`
|
||||
and `DEMO_WEBHOOK_SECRET`; the secret is required for signed delivery.
|
||||
- Test/local configuration may leave SES and webhook settings blank, in which case delivery is
|
||||
reported as skipped while the one-time API response remains the credential handoff.
|
||||
- The remaining gate is a restricted-environment test with verified SES sender/AWS credentials and
|
||||
webhook secret, followed by operator approval. No live SES/webhook call, production operation,
|
||||
deployment, or push was performed.
|
||||
|
||||
## Documentation updated
|
||||
|
||||
- `docs/HANDOFF.md` — added the current Demo SaaS state, verification totals, required configuration,
|
||||
and explicit operational gates.
|
||||
- `README.md` — added a minimal Demo SaaS roles/permissions/trial note and pointed to
|
||||
`backend/.env.example` for demo/SES/webhook environment configuration.
|
||||
406
docs/engineering-log/2026-08-24-training-security-review.md
Normal file
406
docs/engineering-log/2026-08-24-training-security-review.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# 2026-08-24 — Training flow and security review checkpoint
|
||||
|
||||
## Goal
|
||||
|
||||
Move the regular-user journey to:
|
||||
|
||||
`Training → สินค้าของฉัน → เลือกสินค้า → ดู Personas → เริ่มฝึก`
|
||||
|
||||
while preserving the admin flow, restricting demo users, enforcing tenant/owner
|
||||
boundaries, allowing safe retry of `draft`/`failed` products, and preventing
|
||||
persona/credential leakage.
|
||||
|
||||
## Remediation recorded
|
||||
|
||||
- Regular users create products through their own route; demo users are blocked
|
||||
in both frontend navigation and backend authorization.
|
||||
- Personas links are shown to regular users only for `ready` products.
|
||||
- Owners retain visibility of their private `draft`/`failed` products; admins
|
||||
can retry analysis through the edit flow.
|
||||
- Cross-tenant group IDs are indistinguishable from missing IDs (`404`), and
|
||||
API responses use `is_owned` rather than exposing `owner_user_id`/`creator_id`.
|
||||
- Protected routes require both completed setup (`must_setup is False`) and
|
||||
explicit Terms consent (`accepted_terms is True`); missing markers fail closed.
|
||||
- Session/debrief/persona responses use closed allowlists; provider/storage
|
||||
errors are mapped to stable client-safe messages.
|
||||
- Owner markers and private visibility now form one fail-closed invariant;
|
||||
normal create/update paths reject unowned-private and malformed-owner records,
|
||||
while legacy malformed records are excluded from access and indexes.
|
||||
- Non-ready groups remain visible to their owner/admin for recovery, but their
|
||||
persona payloads/counts and direct persona access are empty/blocked until
|
||||
`ready`, including admin responses.
|
||||
- Owner products stuck in `analyzing` remain reopenable and the Training and
|
||||
admin edit UIs expose the recovery action instead of dead-ending.
|
||||
- Judge-only evaluator reasoning and provider paths are excluded from the
|
||||
persona role-play prompt; only bounded role-play state is retained.
|
||||
- Transcript serialization keeps seller/customer messages plus an exact
|
||||
allowlist of server-generated `⏳` scene notes, redacts legacy initial openers,
|
||||
and transcript-only debriefing receives seller messages only.
|
||||
- `finishSetup()` remains setup-required unless the API returns both
|
||||
`must_setup: false` and `accepted_terms: true`.
|
||||
- JWT lifetimes now use a positive 1–720 hour allowlist and token issuance
|
||||
rejects invalid values before encoding; decode also rejects non-positive `exp`.
|
||||
- Group visibility now uses one exact allowlist; malformed/non-string/falsey
|
||||
persisted values are rejected at listing and ID-addressed authorization.
|
||||
|
||||
## Current verification
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| `cd backend && ./.venv/bin/python -m pytest -q` | **457 passed** |
|
||||
| `cd frontend && npm run test:unit` | **18 passed** |
|
||||
| `cd frontend && npm run build` | **passed** |
|
||||
| `cd frontend && npm run test:e2e` | **15 passed** across desktop, 320×568, and 500×768 |
|
||||
| `cd backend && python3 -m compileall -q app tests` | **passed** |
|
||||
| `git diff --check` | **passed** |
|
||||
| Added-line static security scan | **0** shell-injection, eval/exec, pickle, or formatted-SQL matches; hardcoded-secret pattern matched test-only fixture literals only, with **0 production-file matches** |
|
||||
|
||||
The Playwright run emitted Vite proxy connection-refused messages for
|
||||
`/api/auth/oauth/config` because no backend was running for the fixture server.
|
||||
The tests still passed; this checkpoint does not claim live-provider or
|
||||
production verification.
|
||||
|
||||
## Independent review gate
|
||||
|
||||
`deleg_6017c670` was dispatched against the current tree with four bounded
|
||||
scopes. All four reviewers timed out after roughly ten minutes and returned no
|
||||
complete five-key JSON payload. This is **no verdict**, not a failed review and
|
||||
not approval. Fresh batch `deleg_ea020089` then targeted the post-hardening
|
||||
tree, but its auth, tenant/group, and frontend scopes timed out at 600 seconds
|
||||
and its integration task was interrupted. It returned no complete five-key
|
||||
verdict and is also **no verdict, not approval**. Replacement integration review
|
||||
`deleg_513b1aa3` also timed out at 600 seconds with no complete five-key payload;
|
||||
it is **no verdict, not approval**. The gate remains closed until replacement
|
||||
scopes return complete, schema-valid current-tree verdicts with `passed: true`:
|
||||
Fresh batch `deleg_5c88269e` also targeted the post-hardening tree, but its
|
||||
delegation owner exited before recording a terminal result. It is
|
||||
unknown/no-verdict and cannot affect the gate.
|
||||
Bounded batch `deleg_fc7b3f38` returned one valid negative auth verdict whose findings were remediated, one valid positive tenant verdict, and no usable frontend/integration verdicts, so it did not approve the batch. Latest batch `deleg_73452135` timed out in all four scopes after 600 seconds with no complete five-key payload; it is **no verdict, not approval**. Replacement batch `deleg_191e6862` completed with one valid negative auth verdict, one valid positive frontend verdict, one timeout, and one valid negative lifecycle verdict; its findings were remediated and it did not approve the tree. Fresh batch `deleg_71b85ab4` completed with one valid frontend pass and three timeouts (auth, backend visibility/privacy, and lifecycle); the timeouts are no verdicts, so this batch did not approve the tree. Replacement batch `deleg_b4bdbacb` completed with four valid negative current-tree verdicts; all cited findings were remediated locally and re-verified by focused/full automated tests. Earlier batch `deleg_40657086` produced one valid positive auth verdict, but its group, chat/privacy, and lifecycle scopes were interrupted without complete five-key payloads; it is incomplete/no-approval. Interrupted batch `deleg_735d9a1f` produced no complete five-key payload in any scope, so it is **no verdict, not approval** and is superseded by the current batch. Fresh batch `deleg_4c6a7118` is now running against the exact current tree with eight bounded scopes and an exact five-key schema. It includes a regression gate for the corrected super-admin CSV privacy bypass and malformed active-session recovery. No stage, commit, or push is allowed until every required scope returns a complete `passed: true` verdict with empty blocking arrays.
|
||||
|
||||
Older batch `deleg_3fdefb96` completed before the latest hardening and is
|
||||
superseded; its findings are not approval or a current-tree verdict.
|
||||
Batch `deleg_b25d8482` likewise completed from a pre-hardening snapshot and is
|
||||
superseded; only a fresh current-tree verdict can affect this gate.
|
||||
Batch `deleg_f0a2aa91` also predates the latest hardening and is superseded;
|
||||
its findings do not close or replace the current-tree gate.
|
||||
Batch `deleg_08c54d5a` was also dispatched before the latest hardening. Its
|
||||
auth task was interrupted with no verdict; its tenant and frontend tasks are
|
||||
superseded and cannot affect the current-tree gate.
|
||||
Batch `deleg_cd09ba23` exited before recording a terminal result. It is an
|
||||
unknown/no-verdict batch, predates the current hardening, and cannot affect the
|
||||
current-tree gate.
|
||||
Batch `deleg_abeccba2` was dispatched before the latest hardening; all three
|
||||
tasks were interrupted without complete JSON verdicts and cannot affect the
|
||||
current-tree gate.
|
||||
Batch `deleg_bfac6f8b` exited before recording a terminal result. It is an
|
||||
unknown/no-verdict pre-hardening batch and cannot affect the current-tree gate.
|
||||
Batch `deleg_5f3f325f` exited before recording a terminal result. It is an
|
||||
unknown/no-verdict pre-hardening batch and cannot affect the current-tree gate.
|
||||
|
||||
```json
|
||||
{
|
||||
"passed": true,
|
||||
"security_concerns": [],
|
||||
"logic_errors": [],
|
||||
"suggestions": [],
|
||||
"summary": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## Git and safety state
|
||||
|
||||
- Branch: `main`
|
||||
- HEAD: `8a632b5e6a3b67c9acd2787e1a686ac17ae69486`
|
||||
- Index: clean; no staged paths
|
||||
- Existing user WIP remains untouched
|
||||
- No secrets were read, stored, staged, committed, pushed, or deployed
|
||||
|
||||
## Next action
|
||||
|
||||
Validate exact-current re-check `deleg_0456c47d`. If both exact five-key verdicts
|
||||
pass with empty blocking arrays, dispatch the remaining six scopes as three queued
|
||||
waves of at most two heavy reviewers. If either fails, stop the queue and repeat
|
||||
test-first remediation only for that scope. Commit/push/deploy still require
|
||||
explicit operator direction.
|
||||
|
||||
## 2026-08-25 Wave 2 Chat/session and auth remediation
|
||||
|
||||
Independent batch `deleg_ead5db71` returned two complete negative verdicts.
|
||||
The Chat/session reviewer reproduced three current-tree failures: a second
|
||||
pre-seller customer opener leaked through public serialization, malformed
|
||||
prompt-facing Persona fields could be published as ready, and a corrupt
|
||||
same-scope session status allowed creation of a second session. The auth reviewer
|
||||
reproduced repeatable public super-admin promotion after deleting all users,
|
||||
unbounded forged JWT lifetimes, trivially repeated production secrets, unstable
|
||||
OAuth subject/email identity, implicit OAuth tenant creation, and malformed
|
||||
consent state accepted by `/auth/me`.
|
||||
|
||||
The remediation was test-first. `backend/tests/test_wave2_review_regressions.py`
|
||||
failed all **12** new regressions before production changes and then passed all
|
||||
**12**. The current implementation now:
|
||||
|
||||
- redacts every customer message before the first seller turn;
|
||||
- validates bounded, finite, structurally canonical prompt-facing Persona data;
|
||||
- rejects malformed same-scope session mode/status/outcome before resume/create;
|
||||
- keeps public registration permanently at role `user`; factory bootstrap is the
|
||||
only super-admin initialization path;
|
||||
- bounds decoded JWT lifetime to configured `JWT_EXPIRES_HOURS` and requires
|
||||
positive finite `iat`/`exp`;
|
||||
- rejects trivially repeated production JWT/bootstrap secrets;
|
||||
- persists atomic provider+subject OAuth bindings, rejects email-only auto-link,
|
||||
and requires a pre-provisioned active default organization; and
|
||||
- rejects inconsistent consent/setup state and malformed consent timestamps.
|
||||
|
||||
Compatibility fixes narrowed Persona validation to bounded recursive JSON while
|
||||
retaining strict scalar revealable fields, updated stale tests that encoded the
|
||||
retired email auto-link/first-user promotion behavior, and kept malformed state
|
||||
fail-closed. Current automated evidence is **496 backend tests passed in 71.80s**,
|
||||
`compileall` passed, `git diff --check` passed, and the added-line secret/dangerous-
|
||||
execution scan returned **0 findings**. No live OAuth provider verification is
|
||||
claimed.
|
||||
|
||||
Re-review `deleg_6155669d` returned two complete negative verdicts. Chat/session
|
||||
still silently normalized malformed explicit `tier` and `recontact`; auth still
|
||||
accepted duplicate same-user OAuth bindings, low-period repeated secrets, and a
|
||||
non-null malformed consent timestamp while Terms were unaccepted. Six additional
|
||||
RED assertions reproduced all four classes. The remediation validates explicit
|
||||
Persona behavior fields before defaults/coercion, rejects every repeated OAuth
|
||||
identity pair, detects exact repeated secret periods, and enforces a canonical
|
||||
unaccepted-consent timestamp of null/absent.
|
||||
|
||||
Re-review `deleg_1e21e60c` approved the auth/setup/consent/JWT/OAuth/config
|
||||
scope with an exact five-key pass after **226 focused** and **502 full** tests.
|
||||
It found one remaining Chat-only alias-validation defect: malformed explicit
|
||||
`intent_tier` was skipped when a valid `tier` coexisted. A dedicated RED test
|
||||
reproduced the acceptance; the guard now validates every explicitly present
|
||||
alias before normalization.
|
||||
|
||||
Exact-current evidence after that narrow patch: focused Wave-2 file **19 passed
|
||||
in 1.39s**, full backend **503 passed in 71.89s**, compileall and
|
||||
`git diff --check` passed, and the added-line scan returned **0 findings**.
|
||||
Incremental current-tree Chat re-review `deleg_8c71eff4` returned an exact
|
||||
five-key pass with empty blocking arrays. Its live probes confirmed malformed
|
||||
explicit `intent_tier` fails closed alone and beside a valid `tier`, while
|
||||
canonical nested Persona data remains accepted. The reviewer independently ran
|
||||
**136/136** bounded Chat/session/privacy tests, **19/19** Wave-2 regressions,
|
||||
four targeted tests, compileall, and diff checks. At that historical checkpoint,
|
||||
Wave 2 was closed and the overall gate was **4/8**; Wave 3 batch
|
||||
`deleg_1733a8bf` then reviewed analytics/privacy and frontend with exactly two
|
||||
read-only reviewers while Wave 4 remained queued. The final state is recorded
|
||||
in the Wave 3–4 completion section below.
|
||||
|
||||
## Wave 1 current-tree review and remediation
|
||||
|
||||
`deleg_4d48caf2` returned two schema-valid negative verdicts rather than timing
|
||||
out. Ownership review proved that `super_admin` could cross the owner/private
|
||||
boundary in group listings, ID-addressed reads, report/Persona reads, and chat.
|
||||
Lifecycle review proved three independent gaps: privileged malformed-ready reads,
|
||||
unvalidated status/transitions, and stale admin variant publication after source
|
||||
replacement during generation.
|
||||
|
||||
The fixes were test-driven:
|
||||
|
||||
- Added `backend/tests/test_review_wave1_regressions.py`; its initial run failed.
|
||||
- Excluded owner-private records from super-admin listing and authorization in
|
||||
both group and chat route policy implementations.
|
||||
- Added `GROUP_STATUSES` and explicit transition validation to `GroupStore`.
|
||||
- Validated structurally ready artifacts before any role receives a persisted
|
||||
ready group.
|
||||
- Revalidated the source persona identity under the record lock before appending
|
||||
an admin variant.
|
||||
- Updated stale compatibility tests that previously treated malformed-ready
|
||||
records as readable.
|
||||
|
||||
Verification after compatibility remediation:
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| focused Wave 1 + related suites | **125 passed** |
|
||||
| backend full suite | **471 passed in 68.53s** |
|
||||
| frontend unit | **20 passed in 6 files** |
|
||||
| frontend production build | **passed; 1775 modules transformed** |
|
||||
| Playwright desktop + 320×568 + 500×768 | **15 passed in 3.6s** |
|
||||
| compileall / `git diff --check` | **passed / passed** |
|
||||
| added-line security scan | **0 findings** in checked secret, shell, SQL, and debug patterns |
|
||||
|
||||
Remediation re-check `deleg_8d258a4f` was dispatched with exactly two heavy
|
||||
reviewers. The six untouched scopes remain queued. No stage, commit, push, deploy,
|
||||
reset, stash, credential, or permission action was performed.
|
||||
|
||||
## 2026-08-25 second lifecycle remediation
|
||||
|
||||
`deleg_8d258a4f` returned two complete five-key verdicts. Ownership/tenant
|
||||
isolation passed with empty blocking arrays. Lifecycle/races/readiness/variants
|
||||
failed on five confirmed classes: malformed-ready privileged indexes,
|
||||
`publish_analysis()` transition bypass, duplicate Persona IDs, stale same-ID admin
|
||||
variant publication, and missing trainee source revalidation.
|
||||
|
||||
The second remediation was test-first. Six new assertions failed before the patch.
|
||||
The service now excludes malformed lifecycle records from role indexes, publishes
|
||||
analysis only from `analyzing`, and rejects duplicate Persona IDs. Both admin and
|
||||
trainee variant paths now re-authorize and compare the complete source Persona,
|
||||
sales kit, and safe input snapshot under the source-group lock before publication.
|
||||
A stale board expectation was updated to match the canonical fail-closed contract.
|
||||
|
||||
Current evidence:
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| focused lifecycle/remediation set | **69 passed in 10.90s** |
|
||||
| backend full suite | **477 passed in 69.17s** |
|
||||
| compileall (`app tests`) / `git diff --check` | **passed / passed** |
|
||||
| added-line static scan | **0 production findings**; 3 password matches are test fixtures |
|
||||
|
||||
Because the patch touched the shared visibility boundary, both Wave 1 scopes were
|
||||
re-dispatched against the exact current tree as `deleg_0456c47d`, with two
|
||||
read-only reviewers and no overlapping review wave. The other six scopes remain
|
||||
queued. No stage, commit, push, deploy, reset, stash, credential, or permission
|
||||
action was performed.
|
||||
|
||||
## 2026-08-25 third Wave 1 remediation
|
||||
|
||||
`deleg_0456c47d` returned two complete five-key negative verdicts. Ownership
|
||||
failed because role-first authorization denied a private product to its own user
|
||||
after promotion to admin or super-admin. Lifecycle failed because the variant
|
||||
snapshot omitted visibility and had no monotonic signal for a structurally
|
||||
identical reanalysis.
|
||||
|
||||
Five RED regression instances reproduced the exact gaps. The current policy now
|
||||
recognizes a valid same-tenant owner before applying privileged non-owner denial,
|
||||
while malformed owner records retain their prior fail-closed 403/404 contracts.
|
||||
Privileged-owner listing uses the closed owner envelope (`is_owned`) rather than
|
||||
exposing `owner_user_id`. Every successful analysis publication increments an
|
||||
`analysis_revision`; variant generation snapshots and revalidates both that
|
||||
revision and visibility under the source lock for admin and trainee paths.
|
||||
|
||||
Current evidence:
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| RED Wave 1 regression file | **5 expected failures; 17 passed** |
|
||||
| focused lifecycle/ownership/race set | **74 passed in 12.12s** |
|
||||
| malformed-owner contract + Wave 1 set | **24 passed in 3.55s** |
|
||||
| backend full suite | **482 passed in 70.14s** |
|
||||
| compileall (`app tests`) / `git diff --check` | **passed / passed** |
|
||||
| added-line static scan | **0 findings** |
|
||||
|
||||
Fresh exact-current re-review `deleg_a5931b0d` is pending with exactly two
|
||||
read-only reviewers. The six untouched scopes remain queued; no overlapping wave
|
||||
was launched. No stage, commit, push, deploy, reset, stash, credential, or
|
||||
permission action was performed.
|
||||
|
||||
## 2026-08-25 promoted-owner response and frontend remediation
|
||||
|
||||
`deleg_a5931b0d` returned two complete five-key verdicts. Lifecycle/races passed
|
||||
with empty blocking arrays. Ownership failed because promoted owners received
|
||||
`is_owned=false` from detail/analyze serialization, their private products were
|
||||
hidden by `Training.vue`, and the router denied an admin owner from Personas.
|
||||
|
||||
The ownership gaps were reproduced before the patch: backend Wave 1 had **2
|
||||
expected failures**, and the frontend Training/router slice had **3 expected
|
||||
failures**. Serialization now derives ownership from the canonical valid
|
||||
owner-private predicate rather than current role. Training renders a privileged
|
||||
owner's own-product section when it contains products, and the router admits a
|
||||
non-demo canonical owner before applying privileged shared-product policy.
|
||||
|
||||
Current-tree evidence after remediation:
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| backend promoted-owner/Wave 1 regressions | **22 passed in 3.17s** |
|
||||
| frontend Training + router regressions | **17 passed in 2 files** |
|
||||
| backend full suite | **482 passed in 70.11s** |
|
||||
| frontend full unit suite | **24 passed in 6 files** |
|
||||
| frontend production build | **passed; 1775 modules transformed** |
|
||||
| isolated Sales Trainer E2E | **15 passed in 3.8s; desktop, 320×568, 500×768** |
|
||||
| compileall / `git diff --check` / added-line scan | **passed / passed / 0 findings** |
|
||||
|
||||
The initial E2E attempt reused port 3000 and tested an unrelated CrowdSight Vite
|
||||
server; the served title proved the collision. No product change was made for
|
||||
that infrastructure failure. An isolated Sales Trainer server on port 3001 passed
|
||||
all 15 cases and was then stopped. Fresh ownership-only reviewer
|
||||
`deleg_457c2d44` is pending with one read-only agent. Lifecycle remains approved;
|
||||
the remaining six scopes stay queued. No stage, commit, push, deploy, reset,
|
||||
stash, credential, or permission action was performed.
|
||||
|
||||
## 2026-08-25 cross-tenant promoted-owner remediation
|
||||
|
||||
Fresh ownership reviewer `deleg_457c2d44` reproduced a blocking stale-record
|
||||
path: a promoted super-admin could list, read, and start chat against a private
|
||||
record in another tenant when that record reused the actor's user ID. The prior
|
||||
owner predicate validated identity and private visibility but allowed the
|
||||
super-admin role to bypass tenant equality.
|
||||
|
||||
Two parameterized regressions covered a different valid tenant and an invalid
|
||||
empty tenant. Both failed before production changes. A single canonical helper
|
||||
now requires a valid matching actor/group tenant together with owner ID and
|
||||
private visibility. List serialization, group authorization, `is_owned`, and
|
||||
chat authorization use that policy. Cross-tenant super-admin access remains
|
||||
available only for ownerless shared records.
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| RED stale/invalid-tenant owner regressions | **2 expected failures** |
|
||||
| canonical + stale-tenant focused slice | **4 passed in 1.43s** |
|
||||
| ownership/security focused set | **125 passed in 11.52s** |
|
||||
| backend full suite | **484 passed in 70.52s** |
|
||||
| compileall / `git diff --check` / added-line scan | **passed / passed / 0 findings** |
|
||||
|
||||
The attempted focused command naming nonexistent `test_training_flow.py` ran
|
||||
zero tests and is not evidence; it was replaced by the verified existing-file
|
||||
command above. `deleg_457c2d44` is superseded by this code patch. Fresh
|
||||
ownership-only reviewer `deleg_85d91881` is pending with one read-only agent;
|
||||
the six remaining scopes are still queued. No stage, commit, push, deploy,
|
||||
reset, stash, credential, or permission action was performed.
|
||||
|
||||
## 2026-08-25 Wave 3–4 completion — eight-scope code gate passed
|
||||
|
||||
Wave 3 initially returned two complete negative verdicts in `deleg_1733a8bf`.
|
||||
The analytics scope found unbounded personal/admin store scans and a missing
|
||||
session-user-tenant consistency check. The frontend scope found duplicate analysis
|
||||
actions while status was `analyzing` and a completed regular-user chat link that
|
||||
routed through `/` instead of `/my/board`. All four findings were reproduced and
|
||||
remediated with focused regressions, bounded shared scan accounting, tenant-safe
|
||||
joins, disabled in-progress actions, and role-aware result navigation.
|
||||
|
||||
Fresh Wave 3 batch `deleg_1fed2524` approved the frontend scope but found one
|
||||
remaining analytics export leak: arbitrary nested persisted values were passed to
|
||||
`str()` and emitted into CSV. A RED sentinel regression reproduced leakage from
|
||||
`persona_name`, `scenario`, `created_at`, and a malformed username. `_csv_cell`
|
||||
now exports only bounded strings and finite integer/float scalars; booleans,
|
||||
composites, non-finite numbers, and unsupported values become empty cells.
|
||||
|
||||
Post-fix evidence:
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| nested-value CSV regression | **1 passed in 0.63s** |
|
||||
| focused analytics/export/privacy slice | **39 passed, 146 deselected in 7.88s** |
|
||||
| backend full suite | **509 passed in 73.27s** |
|
||||
| frontend full unit suite | **26 passed** |
|
||||
| frontend production build | **passed; 1775 modules transformed** |
|
||||
| isolated Playwright desktop + 320×568 + 500×768 | **15 passed** |
|
||||
| compileall / `git diff --check` / source security scan | **passed / passed / 0 findings** |
|
||||
|
||||
Analytics re-review `deleg_bcb1510f` returned an exact five-key pass with empty
|
||||
security and logic arrays. It independently confirmed scalar-only CSV handling,
|
||||
shared scan budgets, and tenant-consistent session/user/group joins. Wave 3 then
|
||||
closed at **6/8** approved scopes.
|
||||
|
||||
Final Wave 4 batch `deleg_97eb385c` used exactly two concurrent read-only agents:
|
||||
|
||||
- Scope 7/8 frontend-backend API contracts: **passed**, empty blocking arrays;
|
||||
reviewer verification included **26/26** frontend tests and **81/81** focused
|
||||
backend contract/security tests.
|
||||
- Scope 8/8 bounded cross-cutting security/correctness: **passed**, empty blocking
|
||||
arrays; backend, frontend, build, compileall, diff check, and isolated
|
||||
three-viewport E2E all passed.
|
||||
|
||||
The exact-current local code/security gate is therefore **8/8 independently
|
||||
approved**. Non-blocking follow-ups are to add direct `_csv_cell` parameterization,
|
||||
expand malformed frontend-envelope tests, and prevent Playwright from reusing an
|
||||
unrelated server on port 3000. These do not reopen the gate. No live OAuth-provider
|
||||
verification, production runtime check, stage, commit, push, deploy, reset, stash,
|
||||
credential, or permission action is claimed. Branch remains `main`; HEAD remains
|
||||
`8a632b5e6a3b67c9acd2787e1a686ac17ae69486`; the index is empty and the broad
|
||||
existing working tree remains intentionally dirty.
|
||||
157
docs/test-evidence/2026-08-24-training-security-review.md
Normal file
157
docs/test-evidence/2026-08-24-training-security-review.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Test evidence — 2026-08-24 Training flow/security remediation
|
||||
|
||||
## Scope
|
||||
|
||||
Current uncommitted Training → My Products → Personas → Practice flow,
|
||||
ownership/tenant isolation, lifecycle recovery, session-mode hardening,
|
||||
analytics/export privacy, frontend contracts, and Wave 1 reviewer remediation.
|
||||
|
||||
## Current-tree automated checks
|
||||
|
||||
- Backend: `cd backend && ./.venv/bin/python -m pytest -q` → **484 passed in 70.52s**.
|
||||
- Latest focused lifecycle/ownership/race set → **74 passed in 12.12s**.
|
||||
- Targeted malformed-owner contract + Wave 1 regression set → **24 passed in 3.55s**.
|
||||
- Frontend unit: `cd frontend && npm run test:unit` → **24 passed in 6 files**.
|
||||
- Frontend production build: `cd frontend && npm run build` → **passed**
|
||||
(`1775` modules transformed).
|
||||
- Browser journeys: `E2E_BASE_URL=http://127.0.0.1:3001 npm run test:e2e` →
|
||||
**15 passed in 3.8s** across desktop, 320×568, and 500×768. The first attempt
|
||||
correctly failed because Playwright reused port 3000, which was serving CrowdSight;
|
||||
rerunning against an isolated Sales Trainer server on port 3001 passed every case.
|
||||
- Frontend readiness: `curl -fsS http://127.0.0.1:3001/` → **passed**;
|
||||
temporary process `proc_3697d2f91f57` was killed after E2E.
|
||||
- Python compilation: `cd backend && ./.venv/bin/python -m compileall -q app tests` → **passed**.
|
||||
- Whitespace validation: `git diff --check` → **passed**.
|
||||
- Added-line static scan on the latest lifecycle slice: **0 production findings**
|
||||
for hardcoded secrets, shell execution, eval/exec, unsafe pickle, formatted SQL,
|
||||
and debug leftovers. Three password-pattern matches were test fixtures.
|
||||
- Post-`deleg_457c2d44` stale-tenant ownership regressions failed twice before
|
||||
remediation, then the focused ownership/security set passed **125 tests in
|
||||
11.52s**. Canonical private ownership now requires a valid matching tenant
|
||||
together with owner identity, including for promoted super-admins.
|
||||
- `ruff`, `mypy`, `eslint`, and `tsc` are unavailable in this environment; no
|
||||
result is claimed for those tools.
|
||||
|
||||
## Wave 1 independent findings and remediation
|
||||
|
||||
Batch `deleg_4d48caf2` returned two complete, schema-valid negative verdicts:
|
||||
|
||||
1. Ownership/tenant isolation: `super_admin` could list or ID-address owner-private
|
||||
products and start chat against them.
|
||||
2. Lifecycle/readiness/races: privileged reads trusted malformed persisted `ready`
|
||||
state, lifecycle status values/transitions were not validated at the service
|
||||
boundary, and admin variant generation could append a stale variant after
|
||||
concurrent source-persona replacement.
|
||||
|
||||
Current-tree remediation:
|
||||
|
||||
- Owner-private products are excluded from super-admin listings and denied in
|
||||
group and chat authorization; explicitly shared cross-tenant administration
|
||||
remains separate.
|
||||
- `ready` records fail closed for every role unless sales kit, report, and
|
||||
canonical Personas are structurally valid.
|
||||
- `GroupStore` now allowlists lifecycle states and explicit permitted transitions,
|
||||
including `failed → analyzing` retry and `ready → analyzing` reanalysis.
|
||||
- Variant publication revalidates both source status and the exact source persona
|
||||
under the group record lock after LLM generation.
|
||||
- Privileged indexes exclude malformed `ready` and invalid-status records;
|
||||
`publish_analysis()` accepts only persisted `analyzing` groups.
|
||||
- Canonical Persona normalization rejects duplicate IDs, and variant publication
|
||||
compares the complete source Persona, sales kit, and safe input snapshot for
|
||||
both admin and trainee paths.
|
||||
- Added `backend/tests/test_review_wave1_regressions.py` and updated stale tests
|
||||
that previously accepted malformed-ready artifacts.
|
||||
|
||||
## Independent review gate
|
||||
|
||||
Remediation re-check batch `deleg_8d258a4f` returned complete verdicts:
|
||||
|
||||
- Ownership/tenant isolation: **passed** with empty blocking arrays.
|
||||
- Lifecycle/races/readiness/variants: **failed** with confirmed malformed-index,
|
||||
publication-transition, duplicate-ID, and stale same-ID/admin+trainee variant gaps.
|
||||
|
||||
Batch `deleg_0456c47d` then returned two complete negative verdicts: privileged
|
||||
owners lost their own private products after promotion; source visibility was not
|
||||
snapshotted; and structurally identical reanalysis was not detectable. Five RED
|
||||
regressions reproduced those gaps. The current remediation gives valid owners
|
||||
precedence without weakening malformed/non-owner denial, snapshots visibility,
|
||||
and increments/revalidates monotonic `analysis_revision` on every publication.
|
||||
Fresh exact-current re-review `deleg_a5931b0d` is pending with two reviewers and
|
||||
no overlapping wave. The remaining six scopes are queued and not dispatched.
|
||||
|
||||
The gate remains **closed** until all eight current-tree scopes return complete,
|
||||
schema-valid verdicts with `passed: true`, `security_concerns: []`, and
|
||||
`logic_errors: []`. Interrupted, timed-out, truncated, malformed, stale, or partial
|
||||
verdicts do not approve the tree. Any source remediation makes earlier verdicts
|
||||
stale for affected scopes.
|
||||
|
||||
## Harness limitation
|
||||
|
||||
The Playwright fixture server may emit Vite proxy `ECONNREFUSED` messages for
|
||||
`/api/auth/oauth/config` because no Flask backend or OAuth provider is running.
|
||||
This evidence is not a live-provider, authenticated production, or deployment
|
||||
smoke test.
|
||||
|
||||
## Wave 2 remediation evidence — 2026-08-25
|
||||
|
||||
- Failed independent review: `deleg_ead5db71` returned complete negative
|
||||
Chat/session and auth/setup/JWT/OAuth/config verdicts with live local probes.
|
||||
- RED: `tests/test_wave2_review_regressions.py` → **12 expected failures** before
|
||||
production remediation.
|
||||
- GREEN: the same focused file → **12 passed in 1.16s**.
|
||||
- Focused compatibility run: **197 passed / 1 stale fixture failure**, followed
|
||||
by fixture correction; the preceding broader attempt was **157 passed / 29
|
||||
failures**, dominated by one over-strict Persona validator and stale policy
|
||||
expectations, and is not reported as a passing gate.
|
||||
- Full backend: `cd backend && PYTHONPATH= .venv/bin/pytest -q` → **496 passed in
|
||||
71.80s**.
|
||||
- Python compilation and `git diff --check`: **passed**.
|
||||
- Added-line hardcoded-secret/dangerous-execution scan: **0 findings**.
|
||||
- Re-review `deleg_6155669d`: two complete negative verdicts. It reproduced
|
||||
malformed explicit Persona trait coercion plus three auth gaps: same-user
|
||||
duplicate OAuth bindings, low-period repeated secrets, and malformed timestamp
|
||||
state while Terms were unaccepted.
|
||||
- Second RED cycle: **6 expected failures / 12 existing passes** in the focused
|
||||
file before production remediation.
|
||||
- Second GREEN cycle: focused Wave-2 file → **18 passed in 1.40s**.
|
||||
- Latest full backend → **502 passed in 72.06s**; compileall, diff check,
|
||||
Markdown fence check, and added-line scan all passed with **0 scan findings**.
|
||||
- Re-review `deleg_1e21e60c`: auth/setup/consent/JWT/OAuth/config **passed**
|
||||
with exact empty blocking arrays after **226 focused** and **502 full** tests;
|
||||
Chat remained blocked by malformed explicit `intent_tier` coexisting with a
|
||||
valid `tier`.
|
||||
- Third Chat RED cycle: the dedicated coexistence regression failed as expected.
|
||||
- Third Chat GREEN cycle: focused Wave-2 file → **19 passed in 1.39s**; latest
|
||||
full backend → **503 passed in 71.89s**.
|
||||
- Latest compileall, diff check, and added-line scan passed with **0 findings**.
|
||||
- Incremental current-tree Chat re-review `deleg_8c71eff4`: **passed** with
|
||||
exact empty blocking arrays. Independent checks: **136/136** bounded
|
||||
Chat/session/privacy tests, **19/19** Wave-2 regressions, four targeted tests,
|
||||
compileall, and diff checks.
|
||||
- Wave 3 first review `deleg_1733a8bf`: both scopes failed and were remediated.
|
||||
Analytics required shared bounded scans and tenant-consistent user/session/group
|
||||
joins. Frontend required disabled `analyzing` actions and regular-user
|
||||
completed-chat navigation to `/my/board`.
|
||||
- Wave 3 re-review `deleg_1fed2524`: frontend **passed**; analytics found nested
|
||||
persisted values could be stringified into CSV. A sentinel regression failed
|
||||
before the patch and then passed after scalar-only `_csv_cell` handling.
|
||||
- Latest analytics evidence: sentinel regression **1 passed in 0.63s**; focused
|
||||
slice **39 passed, 146 deselected in 7.88s**; full backend **509 passed in
|
||||
73.27s**; compileall and `git diff --check` passed.
|
||||
- Analytics re-review `deleg_bcb1510f`: **passed** with exact empty blocking arrays.
|
||||
- Latest frontend evidence: **26/26** unit tests, production build with **1775
|
||||
modules transformed**, and **15/15** isolated E2E journeys across desktop,
|
||||
320×568, and 500×768.
|
||||
- Final Wave 4 `deleg_97eb385c`: API-contract scope **passed** with **26/26**
|
||||
frontend and **81/81** focused backend checks; final cross-cutting scope also
|
||||
**passed**. Both exact verdicts had empty security and logic arrays.
|
||||
- Independent local code/security gate: **8/8 scopes passed**.
|
||||
- No live OAuth provider, production runtime, deployment, stage, commit, push,
|
||||
reset, or stash verification is claimed.
|
||||
|
||||
## Git and safety state
|
||||
|
||||
- Branch: `main`; HEAD remains `8a632b5e6a3b67c9acd2787e1a686ac17ae69486`.
|
||||
- Existing broad user WIP remains uncommitted and unstaged.
|
||||
- No secrets were read or stored. No reset, stash, cleanup, stage, commit, push,
|
||||
deploy, credential, permission, or production operation was performed.
|
||||
Reference in New Issue
Block a user