Predecessors are real references now
- data.predecessors holds work-package ids, replacing a free-text SOP phase label
that couldn't express "WP04 waits on WP02" and gated nothing. The SOP phase
survives beside it as the descriptive "Sequence phase" field.
- readiness() has two gates: constraints clear AND every predecessor Closed. The
banner, sticky bar, left rail, dashboard Gates column and the ready counters all
reflect the second one.
- Enforced server-side by enforce_release_gates() on every path that sets a status
— the plain upsert included, since that's how the browser and the offline outbox
save. /issue and /status would otherwise have been ways around it.
- Cycles are refused directly and through a chain, with a message naming the
package that already waits on this one. The Creator's picker also hides itself
and its own descendants, so a cycle is hard to build in the first place.
- A deleted predecessor does not block: it would freeze everything downstream of a
package someone removed.
- The gate is refusable, on purpose. Planners release ahead of upstream close-out,
so an explicit reason (data.gateOverride) allows it, gets a gate_overridden audit
event naming what was skipped, and prints on the package. A blank reason is not
an override, and changing the predecessor set clears it. The dashboard won't
release a blocked package at all — it points at the form where the reason is
captured.
Critical constraints reopened after release
- Reopening a SOP-critical constraint on a released package emails the owner, PM,
CM and the package's distribution list (minus whoever did it) and writes a
constraint_reopened audit event.
- Detected by diffing the incoming constraints against the stored ones inside the
normal upsert rather than via a new endpoint: the sync outbox only replays
POST /api/wps, so a dedicated route would be lost offline. It fires only on a
real cleared→open transition, so re-saving an already-open constraint doesn't
re-announce, and never before release or for a non-critical constraint.
- Bodies carry the constraint name, WP number and a link — never package contents.
Verified: 139 API checks on one fresh database (44 permissions + 22 password reset
+ 34 search/localization + 39 gates/notifications), including every bypass path,
cycle shapes, the deleted-predecessor case, blank-reason overrides, and the four
recipients confirmed both in the outbox and on the wire against a local SMTP sink.
27 driven UI checks against the real Creator page in headless Chrome covering the
picker, the override prompt (accept and cancel), override invalidation, the cycle
exclusions and the dashboard refusal. Screenshots reviewed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Site comments (8/3)
- BIM card: LOD removed, IFF # added next to the coordination status, and required
once that status is "Signed off (IFF)" — an unnumbered sign-off isn't traceable.
A LOD already stored on a package is preserved and shown as legacy, not blanked.
- The blue "from SOP types" subtext under a field is now a SOP chip on the label
with the detail in a tooltip. The chip stays visible rather than hover-only:
field tablets have no hover, and "this came from the SOP" is the part that
matters. The hint elements stay in the DOM (hidden) so the code writing to them
keeps working; an observer mirrors their text into the tooltip.
- Specification Section is no longer typed per package. Each WP type carries a
spec section on the SOP; the field is read-only in the Creator and follows the
type, with the SOP's spec folder linked underneath. This reads both spec
comments as one intent — stop typing it, derive it.
- Assignees and Distribution are multi-selects over the SOP project team, showing
each person's job function, with the CM pre-added to Distribution (removable per
package) and a free-text option for people with no account. The stored display
strings are unchanged so print/export/dashboard keep working; account ids ride
alongside for the notification work in wave 3.
Localization + time
- Per-user locale/timezone (Language & time in the user menu), an app-wide default
in the admin console, then the browser. Timezones are validated against the
server's zoneinfo and the picker is fed from it. Calendar dates are formatted
from their parts so a due date never reads a day early in another zone.
- Every displayed timestamp now goes through the shared helpers.
Top-bar chrome
- Project switcher beside the logo and a centered global search, injected into
either generation of top bar; skipped in an iframe so the embedded Creator
doesn't get a second one. Ctrl/Cmd-K focuses search.
- GET /api/search covers work packages, projects and SOPs, scoped to the caller's
projects, hiding archived packages, with LIKE wildcards escaped.
Fixed along the way: showForm() cleared every card's inline display, which undid
applyKind() — so the Package Type and BIM cards reappeared on an install-only
project. Split out applyKindVisibility() and re-apply it there.
Verified: 100 API checks on a fresh database (44 permissions + 22 password reset +
34 search/localization), 24 driven UI checks against the real Creator page in
headless Chrome (SOP chips, both people pickers, spec auto-fill, critical tags,
BIM suppression), and the chrome harness on both bar styles. Screenshots reviewed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Acts on the site comments from 8/3 plus the follow-ups. Foundation work first —
four of the comments all needed the project team to resolve to real user accounts.
Permissions vs project role (new)
- User.role is now the PERMISSIONS role: admin | project_admin | project_user.
project_admin may delete work packages, change a SOP after it is complete, and
delete a project; project_user may not (archiving a WP is still open to them).
Enforced by require_project_admin() server-side; the UI only hides dead ends.
- New User.project_role holds the person's JOB FUNCTION on the project. It grants
nothing — it feeds the SOP team pickers and notification routing.
- Admin console shows both columns and explains the difference. Migration rewrites
the legacy role 'user' to 'project_user'.
- Deleting a project was previously open to any member and unaudited; it now needs
project_admin and writes an audit event. ProjectData.remove no longer drops the
project from the local cache when the server refuses.
SOP project team from user accounts
- PM/APM/CM/QM and additional team members are pickers over the project's members,
storing the account id next to the display name. A name from an older SOP with no
matching account is kept and flagged rather than dropped.
- The WP Creator lists the SOP team first in the Owner picker, and a new package
defaults to whoever is creating it.
Critical constraints
- SOP constraints carry a Critical flag; buildConstraints() now copies the whole
definition through to the package (it previously reduced them to names, losing
description too), and critical rows are marked in the WP form. The email on
reopen-after-release is wave 3.
Password reset by email
- login.html gains Forgot password and a set-a-new-password view, offered only when
the server reports email is actually configured.
- Single-use signed token (AUTH_RESET_MINUTES, default 60) bound to token_version,
sent immediately rather than through the notifications outbox so a reset link is
never persisted. Identical response for unknown accounts; per-account send
cooldown; a completed reset clears any login lockout.
- Session and reset tokens are no longer interchangeable.
BIM kill-switch
- New admin Features card with bim_enabled, OFF by default. The SOP creator hides
the BIM section and the Creator treats every package as install-only while it is
off; a SOP that already has BIM keeps its data untouched.
Verified with two throwaway-database test scripts: 44 checks on the permissions
matrix and token handling, 22 on the reset flow end-to-end against a local SMTP
sink (real message captured, link extracted and used). Front-end files parse-checked
in headless Chrome. Not yet exercised in a browser against a real login.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings the Work Package Suite from a browser-local prototype to a
multi-tenant, SQL-backed deployment hardened for customer IP.
Auth & access control
- Local username/password login (bcrypt + JWT in an HttpOnly cookie),
admin-managed users, per-project membership, and project-scoped API access.
- Admin console: change user roles, view the audit trail, manage settings.
Security hardening
- CSP / HSTS / X-Frame-Options / nosniff headers in nginx; Secure cookie via
X-Forwarded-Proto; CSRF Origin check; attribute-safe output escaping.
- Login lockout, token_version session revocation, stronger password policy,
fail-closed secret loading, encrypted (AES-256) database backups.
Persistence & schema
- SOPs and Work Packages are now DB-backed and shared across users, written
through a durable client sync outbox that queues offline edits.
- Alembic migrations applied automatically on container start.
New capabilities
- Phase 2 dashboard (progress, gating, pagination, archive).
- Phase 3 PWA "Field View" with offline caching and auth fallback.
- WP owner assignment with OPTIONAL email notifications, OFF by default and
toggled from the admin console. SMTP password is read only from the
SMTP_PASSWORD env var (never stored); emails carry a WP number + deep link,
never customer IP.
Also: IBM Carbon restyle, Help section, and DEPLOYMENT.md brought up to date.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The api crash-looped because DATABASE_URL had an un-encoded special-char
password (@/!), so SQLAlchemy parsed part of the password as the host
("...@db" → name resolution failure).
db.py now prefers building the connection from POSTGRES_USER/PASSWORD/DB via
SQLAlchemy URL.create(), which encodes the password automatically — any
password works with no manual escaping. DATABASE_URL remains an optional
override (still must be hand-encoded if used). docker-compose now passes the
POSTGRES_* vars to the api container; DEPLOYMENT.md updated (incl. a Portainer
env-vars note).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
server/seed_demo.py seeds a realistic DEMO project (complete SOP + a spread of
Work Packages: issued, gated, multi-discipline master with split instances,
overdue, over-threshold draft) via the API. --clean removes it. DEPLOYMENT.md
documents both smoketest.py and seed_demo.py, including the localStorage caveat
(seeded project shows in the UI picker; seeded SOP/WPs are SQL-only until Phase 2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- API smoke test (server/smoketest.py): stdlib end-to-end check of health,
projects, SOPs, WPs, the AWP issue gate (409 → 200), status, metrics,
comments, and cascade delete. Referenced from DEPLOYMENT.md.
SOP config:
- Constraints: fix custom constraints never appearing — renderStandardConstraints
no longer clobbers state.constraints; customs render in their own list with
remove buttons; modal gains a free-text "Add" field.
- Sources: add column headers (Data Type / Location-Platform / URL / Notes);
preset data types are now fixed labels, "Add Source" creates an editable
custom row.
- Issuance strategy: add a tooltip + worked examples for each option.
- Remove the "Comment submitted" acknowledgement popup (home + suite); keep the
commenter name between comments.
WP creator:
- Clearing the last open constraint now offers to mark the package Issued and
scrolls to the status control.
- Form sections are collapsible (click a section heading to fold it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the stale non-Docker/systemd guide with an admin-facing, start-to-
finish guide for the actual stack (nginx serving html/, FastAPI api, Postgres
db). Covers prerequisites (external proxy network), the root .env credentials,
reverse-proxy wiring, bring-up, and verification. Adds the current data model
(projects + project_id/parent_id/issued_at columns), the full endpoint list, a
"what's stored in SQL today vs Phase 2" table, backups, and the schema-migration
caveat (create_all adds tables, not columns). Points to server/README.md for the
deep container reference.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server/: FastAPI app with SQLAlchemy models for sops, work_packages, comments
- Endpoints for SOP/WP upsert+list+get+delete and comment create+list;
/api/feedback kept as an alias so the existing client keeps working
- Portable across engines (PostgreSQL prod, SQLite dev fallback)
- requirements.txt, .env.example, and server/README.md (Postgres + systemd)
- NGINX now proxies /api/ to the API (replaces the Power Automate hop;
comments persist to SQL)
- Rewrite DEPLOYMENT.md for the API + database architecture
- Add .gitignore for venv/.env/sqlite
Phase 2 (wire the client apps to the API) is next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add nginx-wp-suite.conf: static site + /api/feedback proxy to the Power
Automate trigger (SNI on, Host header, POST-only, body cap)
- Remove IIS web.config
- Update feedback-config.js comment and DEPLOYMENT.md to the NGINX setup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Set FEEDBACK_ENDPOINT to same-origin /api/feedback (no CORS, hides trigger URL)
- Add web.config with ARR/URL-Rewrite proxy rule (placeholder trigger URL),
HTTPS/POST/static-content setup
- DEPLOYMENT.md: concrete IIS + Power Automate steps and the HTTP-trigger
Request Body JSON Schema matching the app payload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WP type discipline removal:
- SOP WP Types step now has Enabled / Special Rules-Notes / WO Complete
Approval columns (discipline column and DISCIPLINES/type-discipline maps gone)
- WP Creator: drop the derived Discipline field, the type-trade number code,
and discipline from saved packages and output; number tokens are now generic
- Default WP number format no longer includes [Discipline]
- Harden buildConstraints against object-shaped constraint names
Firewall hosting:
- Remove external Google Fonts @import; fall back to system fonts (no
outbound calls, runs fully behind a firewall)
Feedback collection:
- Add shared feedback-config.js with a single FEEDBACK_ENDPOINT hook +
best-effort postFeedback() (backend or Power Automate/SharePoint)
- Add Export / Import to home and SOP feedback; wire central-post on all three
surfaces (home, SOP step comments, WP review comments)
- Add DEPLOYMENT.md documenting hosting and both feedback paths
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>