Skip to content

History

Revisions

  • Roles dev guide: say "two", not "one" — the page contradicted itself It opened with "the manifest is the single declaration… there is no second list to keep in step", and then two paragraphs later told you to write two things. A developer reading that would reasonably ask which it is. It is TWO, and that is now the first thing on the page: 1. the NAME — a Cap:: constant 2. the DETAILS — the module's manifest entry …with a diagram of the five things generated from them, and — the part that was missing — WHY it cannot be one. The name has to be a real constant, a constant has to exist before the code runs, and generating one needs a build step this project deliberately doesn't have. And the constant isn't ceremony: it IS the safety property. Cap::ASSETS_VCENTER misspelled is an immediate fatal error; 'assets.vcentre' misspelled is a silent permanent 403 the administrator never sees. So the second list is the one worth having, and two is the floor. Also clarifies that the residual block in settings_keys.php is a TO-DO list of unconverted modules, not a duplicate — it empties as the roll-out proceeds. The moving-parts table is demoted below this and now marks each row as hand-written or derived, so it reads as reference rather than as the lede.

    @edmozley edmozley committed Jul 13, 2026
    c84b425
  • Wiki: split the PHP floor page, refresh Roles & Permissions, restructure the sidebar The PHP floor page had grown to 861 lines and was three documents in a trench coat: a decision, a language tour, and a deep argument about permission keys. Split into three, each standing alone: Raising-the-PHP-Floor (307) the DECISION — where we are, who breaks, the verdict, and the constants-to-enums bridge PHP-8-Language-Features (261) the 101 tour of 8.0/8.1 with before/after from real code, including the features that WOULDN'T help us, said plainly PHP-Enums-and-RBAC (353) the one feature the argument turns on — and the reason a capability is a Cap:: constant and never a string. Worth reading even if the floor never moves, because it explains the permission system. Section numbers and cross-references rewritten by meaning rather than by find-and-replace, since the sections changed pages. Roles & Permissions was stale: written before the manifest became the single declaration and before the endpoint audit existed. Now says that capabilities are derived from one file per module (and why the four-list version was the bug), and carries a table of what D005 has actually found — the shared settings endpoint, six Intune endpoints, all 49 RFP Builder endpoints, unauthenticated audit-log forgery, and an LMS learner reaching the authoring settings. Every one found by hand, by accident, which is the point. Sidebar: the security pages are now grouped by the three layers they belong to (module access → capabilities → administrator), rather than a flat list, and all nine RBAC/PHP pages are linked. The two new PHP pages nest under the floor page.

    @edmozley edmozley committed Jul 13, 2026
    19e7234
  • PHP floor page: note that the registry is now derived from manifests The code samples in section 3.1 described rbacCapabilities() as current. They aren't any more — capabilities are Cap:: constants and the registry derives from the settings manifests. Kept the samples (they show the string-keyed shape the enum argument is about, which is precisely what the constants replaced) but flagged them so nobody reads them as today's code.

    @edmozley edmozley committed Jul 13, 2026
    9202652
  • RBAC: the manifest is now the single declaration, and document D005 Roles — Developer Guide: the recipe changed. The capability registry, the module list and the setting-key map are all DERIVED from <module>/settings/manifest.php now, so adding a capability is a constant plus a manifest entry — there is no capRegistry() to hand-edit and no settings_keys.php row to promote. Calls out the one seam derivation doesn't cross (a Cap:: constant no manifest claims, which is a capability nobody can hold — so any guard using it 403s everyone but admins, permanently and invisibly to the admin who bypasses the check). Adds a section on D005, the endpoint audit, and the discipline behind it: type safety catches a MISSPELLED permission, nothing catches one nobody wrote. Lists the five holes that were each found by hand, by accident. And the rule that keeps the tool useful — if you add an auth mechanism, teach it, because its first run gave five criticals of which four were false alarms, and a scanner that cries wolf is ignored within a week. Security: adds the auditing section so the tool is discoverable from the security page, not just the developer guide.

    @edmozley edmozley committed Jul 13, 2026
    da7eb1c
  • RBAC: bring the wiki up to date with per-tab capabilities Both Roles pages predated the per-tab decision and described a system that no longer exists: rbacCapabilities() (now capRegistry), bare-string guards (now Cap:: constants), "one manage per module, split later" (we went per-tab), and a rename that orphans grants and needs a data migration (there's an alias map now). Neither mentioned the settings manifest, the shared-endpoint key ownership, the sensitive badge, the umbrella, or the security fix. Roles & Permissions — rewritten around the per-tab model: why a tab is the unit (vCenter credentials sit next to a lookup list), the umbrella, sensitive badging, and a full "how it's enforced" section covering all three mechanisms — panels not rendered rather than hidden, hard guards on every write endpoint, and per-KEY authorisation on the shared settings endpoint (including the #829 hole it closed). Plus what is deliberately NOT guarded, and why gating a read breaks the module. Roles — Developer Guide — rewritten as the extension guide: why capabilities are constants (a mistyped string 403s silently and is invisible to admins, who bypass the check — so the typo becomes a privilege escalation), adding/renaming/removing a capability, and the full six-step recipe for converting a module, worked against Asset Management. Now carries the traps that actually bit: a get_* endpoint that isn't a read (get_vcenter performs the sync and Servers calls it), endpoints with no guard at all, and the reminder that converting a module is an audit of it. Plus the three-actor HTTP test matrix and a checklist. Security — the Authorisation section was wrong, not just stale: it claimed the System module "cannot be disabled" and is always accessible to active analysts, which stopped being true when is_admin shipped. Rewritten as the three layers, each with its gate and its default, and the note that hiding a button is never the control. Raising the PHP floor — made the constants-to-enums bridge concrete rather than asserted. Shows the actual migration: call sites are a symbol rename an IDE can do and the compiler verifies (a missed one FATALS; you cannot half-finish it), against hunting 200 string literals where a miss keeps working and a typo 403s in silence. The real work is confined to one file. Also states the honest caveat: constants make the typo loud, an enum makes the wrong thing impossible.

    @edmozley edmozley committed Jul 13, 2026
    7d62666
  • Blue sky: raising the PHP floor to 8.1 The case for dropping PHP 7.4/8.0 to unlock enums for the RBAC capability system, plus a 101 on what PHP 8.0/8.1 actually added, with before/after examples from real FreeITSM code. Parked, and the page says why: class constants close the dangerous failure mode (a typo in a capability key silently 403-ing, invisibly to the admin) without leaving 7.4. What the enum adds on top is elegance, not safety, and that doesn't justify breaking every 7.4 user's upgrade path. Documents the constant-to-enum bridge so the swap stays cheap, and the triggers for revisiting. Linked from the Blue Sky hub and the sidebar.

    @edmozley edmozley committed Jul 13, 2026
    c3a6df3
  • LMS authoring + AI + Roles: new deep-dive pages, refreshed LMS hub - LMS.md: rewritten for the two course kinds (authored + SCORM), the learner/manager split, correct table names, links to the new sub-pages. - LMS-Authoring.md: native content model, editor, native player, server-side grading (answer key never on the wire), the CMI-reuse trick, learner/manager enforcement, SCORM upload safety. - LMS-AI-Authoring.md: mechanics of the three AI helpers (outline / article→lesson / lesson→quiz) — grounding, JSON contracts, the draft-never-writes safety model. - Roles-and-Permissions.md: RBAC Layer 2 (settings permissions) — two layers, deny-by-default, is_admin bypass, server-side enforcement, the LMS pilot. - Roles-Developer-Guide.md: add/edit/remove a capability and wire a module's settings in — registry, guards, the operational-vs-settings split, splitting a capability later, gotchas. - _Sidebar.md + Home.md wired up.

    @edmozley edmozley committed Jul 12, 2026
    6f759b8
  • Time-Based Triggers: its own page, a dev recipe, and Pitfalls #9 The #801 write-up was buried mid-page in Workflows, so it never appeared in the sidebar tree — you could only find it if you already knew it was there. New page: Time-Based-Triggers.md — the four triggers and their crons, the fire-once ledger, the fingerprint, the four starter recipes, cron setup, and how to test it by hand without waiting for a scheduler. Plus what was missing everywhere: HOW TO ADD A NEW ONE. Register the trigger, write the detector, choose the entity_key and fingerprint (that choice IS the job — everything else is boilerplate), register with the cron, ship a recipe. With the traps nobody would guess: changing the fingerprint formula re-arms every in-window record AT ONCE, and renaming a trigger silently orphans live workflows (workflows.trigger_event is a plain string nothing validates — the workflow stays active, looks healthy, and never fires again). Pitfalls #9: the fire-once ledger that burned its emission on an audience of nobody. The cron runs from install; by the time you BUILD a renewal workflow the ledger already says "already fired" for every contract in-window, so switching it on leaves it permanently silent for exactly the contracts you built it for — and nothing is observable. Found by asking what happens on a FRESH INSTALL, which the happy path never exercises. Workflows.md keeps a summary + link. Sidebar and See-also updated.

    @edmozley edmozley committed Jul 12, 2026
    0d633c1
  • Workflows: 24 recipes, filterable gallery

    @edmozley edmozley committed Jul 12, 2026
    a17b600
  • Workflows: list the four time-based recipes in Starter templates

    @edmozley edmozley committed Jul 12, 2026
    19ba8c3
  • Workflows: mark the SLA/contract trigger roadmap item done The perl in the previous commit didn't land — bash ate the backticks.

    @edmozley edmozley committed Jul 12, 2026
    83eadab
  • Workflows: time-based triggers, and the problem of not firing twice Every other trigger hangs off a write path. 'The SLA is about to breach' is not an event — nothing happened, time passed — so a cron goes looking. The section leads with the actual hard part: finding a breached SLA is easy; NOT telling you about it 300 times is the feature. A time-based condition stays true, so naive detection re-fires forever. Covers the UNIQUE-key ledger (the database is the arbiter, so overlapping crons can't double-fire), the fingerprint (raise a priority and the SLA target shrinks — the new deadline must be allowed to escalate again, or the workflow goes quiet exactly when the ticket gets more urgent), and why nothing is recorded when no workflow is listening (a real bug caught in testing: it would have made activating a workflow appear to do nothing). Also updates the three-cases summary — 'time passed' is no longer 'not supported' — and marks the roadmap item done.

    @edmozley edmozley committed Jul 12, 2026
    426ad9a
  • Workflows/Webhooks: answer 'what runs when, and what must I schedule?' Ed asked whether making a ticket P1 fires the webhook by itself, or whether a scheduled task has to go looking for it. The facts were all in the wiki but scattered across two pages and written from the implementer's point of view — neither page answered the reader's actual question. Worse, the Webhooks 101 said a webhook means 'no polling, no delay', which is true of DETECTION and wrong about DELIVERY (which sits in a queue until the cron worker sends it). Read literally, it implies nothing needs scheduling — the exact wrong conclusion, and the reason Ed's delivery worker had never run. New section at the top of Workflows.md lays out all three cases plainly: 1. Something happened -> instant, event-driven, NOTHING to schedule. 2. A webhook needs sending -> the delivery worker MUST be scheduled, or messages queue forever. Send test bypasses the queue, so it can pass while every real webhook silently piles up unsent. 3. Time passed (SLA breach, contract expiry) -> not events at all; needs a scheduler we haven't built. Also why no starter template offers SLA escalation. Webhooks 101 corrected with the same warning up front.

    @edmozley edmozley committed Jul 12, 2026
    d2141a7
  • Workflows: the execution log + the Watchtower card Leads with WHY they have to exist: the engine deliberately swallows its own errors so a broken workflow can't break the ticket save that triggered it — which means a failing workflow is otherwise completely silent. Covers the log's filters (and that they live in the URL, which is what makes the Watchtower deep-link possible), the drill-down (conditions with the ACTUAL value compared, so a skipped run explains itself; actions; queued webhooks; the trigger payload snapshot), and that runs survive their workflow's deletion. The card: failed / aborted / dead-lettered webhooks over 24h — the last of which matters because the workflow SUCCEEDED, it queued the send, so a message that never arrived shows up nowhere else. Names names, shows the error, excludes dry runs. Stage 6 items marked done.

    @edmozley edmozley committed Jul 12, 2026
    52e94b3
  • New page: Webhook Message Formats A chat format is nothing but a JSON body template with a {{message}} slot, so they're data now, not a PHP switch — add any platform without a release. Covers: the idea; the five fields; ready-to-paste templates for Google Chat, Mattermost, Rocket.Chat, ntfy, Telegram, PagerDuty and a Discord rich embed (showing static fields and merge codes inside a template); the escaping guarantee (decode -> substitute into values -> json_encode, never splice raw text, with the injection test shown); the engine's hardcoded fallback; why the built-ins are locked and Copy exists; and why the URL pattern and formatting hint exist at all (both are bugs Ed hit today). Webhooks.md section trimmed to a summary pointing here. Sidebar updated.

    @edmozley edmozley committed Jul 12, 2026
    f962a3d
  • Webhooks: message formats are data — add your own platform A chat preset is just a JSON body template with a {{message}} slot, so they're rows now, not a PHP switch. Covers the four fields, why built-ins are locked, the escaping guarantee (decode -> substitute into values -> json_encode, never splice raw text), and the hardcoded fallback.

    @edmozley edmozley committed Jul 12, 2026
    a46b4c2
  • Workflows: {{ticket.number}} — quote the reference, not the row id New section: {{ticket.id}} is the database row id (83); {{ticket.number}} is the reference a requester quotes (ETC-623-64409). Includes the exception that matters — a Ticket ID action ARG must stay {{ticket.id}} because the engine looks the record up by row id. The number goes in the words; the id stays in the plumbing.

    @edmozley edmozley committed Jul 12, 2026
    670366f
  • Workflows/Pitfalls: readable _name merge codes + the lying Send-test preview (pitfall #8) Workflows.md: new 'Use the name, not the id' section — {{ticket.priority_name}} renders Critical, not 4; twins derived from the same registry as the condition dropdowns; never clobbers a host module's value. Pitfalls: #8 — the Send-test sample invented fields the real payload never carries, so a preview rendered 'High' and production rendered ''. Bug #1 reached from the opposite direction. Takeaway: generate previews from the same definition the real thing uses, never a hand-written mock that can drift.

    @edmozley edmozley committed Jul 12, 2026
    93d8a6b
  • Workflows/Webhooks: templates, dry run, merge codes, encryption, retention + 2 new pages Workflows.md: starter templates (with the name->id resolver design), dry run, and a rewritten merge-codes section explaining why the picker is scoped to the trigger (renderTemplate resolves unknown paths to an EMPTY STRING, so an unavailable code fails silently). Refreshed "what's still ahead". Webhooks.md: new "Data protection" section — URL + signing secret encrypted at rest, URL redacted in the delivery log, the VARCHAR(2000) width requirement, why the secret is NOT masked in the editor, and payload retention (default 7d) vs row retention (30d) with the Replay trade-off stated. Plus a TLS troubleshooting pointer. New: HTTPS-Certificates-and-CA-Bundles.md — what certificate verification is, what a CA bundle is in plain English, why Windows hits this, the fix (both php.ini files), how to verify, and why "just disable SSL verification" is the wrong answer. New: Workflow-and-Webhook-Pitfalls.md — seven real bugs, their causes and fixes. Five were INVISIBLE in production (nothing crashed, no test would have failed). Meta-lesson: build the thing that makes the class of bug impossible and it hands you the instances for free. Sidebar: both new pages nested under Workflows.

    @edmozley edmozley committed Jul 12, 2026
    360ddf8
  • Add Module Access developer guide (wiring new modules/pages/endpoints into Phase 3 enforcement); link from page + sidebar

    @edmozley edmozley committed Jul 11, 2026
    d3e088d
  • Module Access Control: document the in-place Access-level toggle; fix team default (0) + drop unbuilt save-time warning claim

    @edmozley edmozley committed Jul 11, 2026
    e08db74
  • Module Access Control: mark phases 2 & 3 shipped; document read-gating follow-up

    @edmozley edmozley committed Jul 11, 2026
    6c3f85b
  • Module Access Control: add implementation-phases status table

    @edmozley edmozley committed Jul 11, 2026
    d56d5ee
  • Module Access Control: correct team default (0, not all) Teams default to granting NO modules (matching team company-access), not all-access — under the default 'most' union mode a team defaulting to all would silently hand every member every module. Clarified the strict-mode lock-out case and the safe-upgrade wording accordingly.

    @edmozley edmozley committed Jul 11, 2026
    54612b2
  • Add Module Access Control page (issue #30) Documents the team-based module access design: mirrors company access (individual + team grants + all-modules flag, one choke-point resolver), the most/least-permissive policy with the strict-mode foot-gun called out, server-side per-module enforcement, the summary/edit UI, the effective-access tool, and the safe-upgrade grandfather. Linked under Security.

    @edmozley edmozley committed Jul 11, 2026
    68294b9
  • Add Admin Access Control technical deep-dive (issue #34 / #781) Developer-facing page on how the System module is restricted to admins: the is_admin flag, the three helpers (analystIsAdmin / sessionIsAdmin self-heal / requireAdminJson), the two enforcement layers (page gate + 41-endpoint API gate), the deliberate scoping (what's NOT guarded and why, incl. db_verify pre-login and the teams-vs-departments boundary), the one-time grandfather back-fill, last-admin protection, and a contributor checklist. Linked under Security in the sidebar.

    @edmozley edmozley committed Jul 11, 2026
    e32fdeb
  • Add CSP hardening blue-sky page (issue #43) Documents what strict-CSP support would take and why it's a large phased refactor rather than a bug fix: ~1,300 inline handlers + 159 inline script blocks, plus a measured risk assessment (defence-in-depth behind existing output escaping; low-to-moderate urgency for a self-hosted internal app). Also captures the working 'unsafe-inline' policy for today. Linked under Blue sky thinking and cross-referenced from Email Rendering & Images.

    @edmozley edmozley committed Jul 11, 2026
    fd91472
  • Add "Blue sky thinking" section for parked ideas New landing page explaining it's a holding area for explored-but-uncommitted ideas (risk / complexity-vs-reward / uncertain demand), with Remote Image Archiving moved under it. Added a sidebar section and back-links.

    @edmozley edmozley committed Jul 11, 2026
    7d3b7d9
  • Add Remote Image Archiving (parked idea) page Records the idea of downloading remote https:// email images at import for privacy/archival/offline, and — prominently — the SSRF danger that keeps it parked (may never ship). Cross-linked from the Email Rendering & Images page.

    @edmozley edmozley committed Jul 11, 2026
    8700007
  • Add Email Rendering & Images technical deep-dive page New developer-facing page covering how inbound email is rendered: inline (cid) vs remote (https) images, the get_attachment.php serving pipeline, the BASE_URL sub-path 404 bug (#777), and the two CSS-isolation fixes — the scoped box-sizing content-box reset (#778) and full Shadow DOM isolation (#779), including why iframe was rejected. Linked from the sidebar under Tickets.

    @edmozley edmozley committed Jul 11, 2026
    1df708e