Skip to content

History

Revisions

  • Search: ⌘K content search and knowledge articles (#1072-#1073) Discussion #53 is now down to attachments. Full-Text-Search: status table updated, and §5.1 gains a note on where the three original questions actually stand. Q1 done, Q3 substantially done, Q2 (attachments) the only one outstanding - plus the fourth question from the thread, whether search would reach Ctrl+K, answered yes. The original assessment table is kept, because its "honest answer today" column is what the discussion was replied to with. Dev guide gains §9a on why articles are indexed by a DIRECT CALL from KnowledgeService while tickets go through the dispatch seam. Tickets needed the seam because three paths create them and share no code; articles have exactly one writer. And the article events are the wrong hook anyway - a new DRAFT fires nothing at all, because knowledge.published is deliberately withheld so workflows do not announce a page nobody can open, yet a draft still needs indexing. Records the two traps that had been designed for and never exercised: NULL tenant_id on an article means shared with EVERY company, the opposite of a ticket (which is why searchCorpusArticleScope exists and had never been called), and audience maps onto is_internal failing CLOSED so a future portal search cannot leak by default. Sections renumbered so 9/9a/9b/9c read in file order.

    @edmozley edmozley committed Aug 14, 2026
    150e63b
  • Search: the index keeps itself current (#1070-#1071) All three search pages said indexing was manual. It is not any more. Searching-Inside-Tickets: the backfill is now genuinely one-off, and the warning that new tickets stay invisible until you re-run it is replaced by what actually happens. Adds the three cases where you WOULD run it again - after correcting the full-text settings, on suspected drift, and after restoring an old backup - because "never" is not true either. "Attachment text" replaces it as the honest what-it-does-not-find entry. Full-Text-Search: status block updated, and §3.5's open question is marked answered. It asked whether to index history or only from switch-on; the answer is both, and the two share one document builder. Command-palette content search moves onto the not-built list, since the discussion asked for it and it is genuinely still missing. Search-Corpus-Developer-Guide gains §9 on the subscriber - why it reindexes the WHOLE ticket rather than the changed row (ordering-immune, self-healing, cheap), why the dispatch must come AFTER the commit, and why the document construction moved out of the backfill: if the two drifted, a result would depend on how a ticket happened to get indexed. And §9a on the trap that will catch the next subscriber too: ticket.created fired from ONE place, the analyst path, so it missed the two channels most tickets arrive through. The mailbox file documents the mistaken belief in a comment.

    @edmozley edmozley committed Aug 14, 2026
    766e319
  • Inventory agent: make the API key location impossible to miss Several people have been caught out by where the key comes from, and there were two separate reasons for it. FreeITSM has TWO unrelated things both called "API keys": Software > Settings > API Keys 40 hex chars, no prefix, table `apikeys` - the inventory agent System > API starts fitsm_, table `api_keys` - the REST API v1, Bearer auth A key from the wrong page fails with "Invalid authorization key", which reads like an expired or mistyped key rather than the wrong KIND of key, so nobody thinks to go looking for a second keys page. Inventory-Agent now leads that section with a callout, a table contrasting the two, and the tell: if it starts fitsm_ it came from the wrong page. Also explains why an asset script's key lives under Software, so the location stops feeling arbitrary - the same endpoint feeds the software inventory. Troubleshooting split "Authorization key missing" from "Invalid authorization key", which are different faults, and added the "Unknown endpoint" row from the user who put /api/v1 in his -ApiUrl. Software.md named the table `api_keys`. It is `apikeys`. That is precisely the confusion this commit is about, so it is corrected with a note rather than quietly.

    @edmozley edmozley committed Aug 14, 2026
    a2f1ac9
  • Update for the Links-strip move (#1068) Equipment and CMDB objects are pills in the Links bar now, not sections below the thread. Both pages updated to match. The dev guide gains section 9 on why: the strip already carried a comment, written for the Jira work, saying a link belongs in the strip rather than a panel of its own. Equipment qualifies by that definition and the original card was the deviation. Also records what the space problem actually was - a section cost a bordered card whether or not it had contents, so an empty ticket paid six times to say "nothing here yet" six times - and why the two are still shown distinctly rather than merged, since a broken mouse will never be a configuration item. Plus the trap: the picker host must be a CHILD of .problem-strip, because mobile.js relocates the strip into a phone sheet by selector and a sibling would be left behind where the user cannot see it. Notes the two loose ends left behind (dead .cmdb-* CSS, and a dead SECTIONS entry in mobile.js) so neither looks intentional later.

    @edmozley edmozley committed Aug 14, 2026
    23a12a0
  • Add "Linking equipment — Developer Guide" (discussion #57) Completes the pair. The user page said what it does; this says why the code looks the way it does. Sections that exist because someone will otherwise undo them: - Why ticket_assets has TWO creator columns where ticket_cmdb_objects has one - analyst 7 and user 7 are different people. - The three gates on the write path, and specifically that gate 3 (same-company) is the one that matters for the exact role which can satisfy gates 1 and 2 together. It is also the one easiest to talk yourself out of. - Why the portal re-checks ownership when the dropdown is already scoped, cross-referenced to the get_users.php leak in #54: a scoped list is not a check. - Why the location clause in the picker's WHERE is load-bearing rather than a nicety, and that an empty result may be missing location data rather than a broken query. - The MERGE_MOVE_DEDUPE trap that produced #1064, written up as the general rule: a wrong column name in a table-driven loop is a silent no-op, not a mismatch. Plus the forged-session recipe (no trailing newline, or PHP reports what looks like an auth failure) and the control set, including that the CASCADE has to be proven functionally because db_verify creates FKs inside an empty catch. Records that the neighbouring .cmdb-* CSS has no dark-mode handling at all - left alone rather than changed as a side effect, but real. Every file named in the table verified to exist; every wiki link resolves.

    @edmozley edmozley committed Aug 14, 2026
    cd73455
  • Add "Linking equipment to tickets" (discussion #57) Covers both halves: attaching an asset to a ticket, and the Tickets tab this creates on the asset. Spells out three things a reader would otherwise have to discover: - The picker searches LOCATION as well as hostname/model/serial/tag, and why that matters. Nobody knows a meeting-room TV's hostname; they know where it is. Includes the warning that this depends on assets actually having locations set, which is missing data rather than a broken search if it comes back empty. - Requesters can only ever pick their OWN equipment, and shared kit is therefore deliberately absent from the portal. Documented as a decision with the two rejected alternatives and why each was worse, rather than left looking like a gap. - What happens to links on merge, delete and re-link, and that a ticket and an asset must share a company. Also flags that CMDB objects and assets are linked to tickets separately and are not the same thing - the two sections sit next to each other in the reading pane and will be confused otherwise. Cross-linked from Assets, Tickets and the sidebar.

    @edmozley edmozley committed Aug 14, 2026
    299fa2c
  • Add "The inventory agent" page, and separate the two certificate problems The PowerShell inventory agent had no page of its own - only eight lines under Assets covering the two endpoints and the API key. Nothing on deployment, scheduled tasks, or certificates, which is where a user reporting a failure this week actually got stuck. Inventory-Agent.md covers running it by hand, the collect-only mode, rolling it out as a GPO scheduled task, what it collects, where it goes, and a troubleshooting table. The certificate section is the bulk of it, because that is the common failure. Notes that the fixes are ordered by safety, that pinning deliberately ignores the name on the certificate (so XAMPP's default localhost certificate stops being a problem), that a thumbprint is not a secret, and that renewing a certificate silently breaks every pinned machine. Also flags on HTTPS-Certificates-and-CA-Bundles that it is the OUTBOUND problem - FreeITSM not trusting Slack - and links across. A user searching the wiki for "certificate" was landing there and reading advice that cannot fix an inbound trust failure. Thumbprint lookup is given per web server. The Cert:\LocalMachine\My route is IIS-only; on Apache, XAMPP, WAMP or nginx the certificate is a file and that store is empty. Links to Inventory-Agent added from Assets, Software and the sidebar.

    @edmozley edmozley committed Aug 13, 2026
    de07da0
  • Asset handover: new wiki pair for discussion #56 New pages (app commits 605811aa + 2aed483e): - Asset-Handover (user) - Asset-Handover-Developer-Guide The dev guide records three decisions worth keeping: 1. The designer is BLOCKS, not a canvas, because the middle of the document is a repeating region -- one row per asset, count unknown until render. A WYSIWYG either cannot express that or makes the admin write a loop. 2. Escape FIRST, substitute SECOND. Merge values are user-supplied (users.display_name arrives from the portal, from mail, from imports) and this document is EMAILED, so getting the order backwards would put stored markup into a mail client. Includes the test that proves it. 3. One renderer serves the printed page, the preview and the email. A preview that is a separate approximation is a preview nobody can trust, and the divergence surfaces on a document somebody has already signed. Also records the users_assets stale-row trap (no foreign key; an INNER JOIN is deliberate), why printing beats generating a PDF, and four honest gaps: no logo in the emailed copy, one default template for the whole install, no record that a handover happened, and 23 untranslated locales. Every named file verified to exist; every link verified to resolve; the factual claims (logo_path null, the 2000-char cap, the INNER JOIN) checked against the source rather than recalled.

    @edmozley edmozley committed Aug 13, 2026
    ec6bf40
  • Notifications: new wiki pair for discussion #55 New pages (app commits bbd49c50 + 75803509): - Notifications (user) - Notifications-Developer-Guide The user page leads with what the bell does NOT do, because the four noise rules are the feature -- a bell that fills faster than it can be read is dead while still appearing to work. The dev guide records two things worth keeping: 1. The bell is a SUBSCRIBER to WorkflowEngine::dispatch(), not a second instrumentation layer. ~32 event types already fired from 48 call sites, so adding a notification type must never mean editing call sites. 2. EVERY failure mode in this feature is silent. Suppressed, rejected, thrown-and-swallowed and correctly-quiet all look like an empty table, so every rule needs a POSITIVE control and not just a negative one. The four bugs found during the build are written up for the same reason -- three of them presented as "notifications just never arrive". Also records three pre-existing broken ticket deep links (tickets/?id= in escalate_ticket.php and engine.php, tickets/?ticket= in forms/approvals.php). inbox.js reads only ?ticket_id=, so those return HTTP 200 and never open the ticket. Verified present, not fixed. Every named file verified to exist; every link verified to resolve.

    @edmozley edmozley committed Aug 12, 2026
    3a593d7
  • Landing page: new wiki pair for discussion #63 New pages (app commit f1673644): - Landing-Page (user) - Landing-Page-Developer-Guide The dev guide leads with the constraint that shaped the whole design: the decision happens before authentication and before anything renders, so user_preferences cannot hold it on its own (analyst_id is NOT NULL) and localStorage cannot hold it at all (PHP cannot see it). The cookie is a cache of the preference, not the setting. Also records, for whoever touches this next: - the stored value is a KEY and never a path, with the attack tests that prove it, because this drives a redirect on the most-visited URL - an endpoint writing several rows without a transaction must finish validating before it starts writing (save_branding blanked the header and footer on a rejected request) - when a server-side upload rule is tightened, grep for the client-side restatements of it (the accept attribute and the help text both still recommended SVG after the server stopped taking it) Cross-linked from Self-Service-Portal, Home and the sidebar. Every named file verified to exist; every link verified to resolve.

    @edmozley edmozley committed Aug 12, 2026
    d44d155
  • Morning check groups and routing: new wiki pair, plus corrections to the module page New pages for discussion #64 (app commit 262d7af8): - Morning-Check-Groups-and-Routing (user) - Morning-Check-Groups-and-Routing-Developer-Guide Both lead with the rule the design hangs on: routing is GUIDANCE, never PERMISSION. No authorisation check anywhere consults AssignedAnalystID -- if it did, the round would stop on exactly the morning it matters most. Corrections to Morning-Checks.md, which had gone stale: - "Raise Ticket from a Check" said the button appears only on statuses whose Requires notes flag is true. It is now on every check. - Settings listed three tabs; there are four. - Key Tables was missing morningChecks_Groups and _ResultLinks, and the CreatedBy / ModifiedBy distinction. - Added the Undo section. Every file named in the dev guide verified to exist; every wiki link verified to resolve.

    @edmozley edmozley committed Aug 12, 2026
    9ad70c4
  • Document service uptime and history, as a pair Plain-language page for whoever runs the status board, and a developer guide covering the derivation, the phase-2 update log and the arithmetic. The worked example Ed asked for is the centrepiece of the plain-language page: a three-day power failure taking out four services, brought back at four different times, with what you actually do at each point and what each service then reports. Those figures are not illustrative — the scenario was built through the real save endpoint as the test fixture, so the table is what the code produced. It also states the thing the feature depends on and which no amount of UI can convey on its own: the history is only as good as the way incidents are updated. Hence the contrast at the end between five saves giving four accurate service histories, and one save at each end giving one blunt figure that is wrong for three of the four. The developer guide records the two compatibility rules that are easy to get wrong later — returning null rather than an empty array for a pre-log incident, and omitting the EXISTS clause entirely when the tables are absent — plus the bug the worked example caught, where a restored service disappeared from its own history because the query still keyed off the current links. Linked from Service-Status and the sidebar.

    @edmozley edmozley committed Aug 12, 2026
    85ee16b
  • Document the requester picker, as a pair Plain-language page for someone raising a ticket on a colleague's behalf, and a developer guide covering the request flow, why authorisation happens twice, and the three bugs the feature fixed on the way through — the unscoped user list, the name box that silently did nothing, and the requester who was created with no company at all. Two things recorded because they are the sort that get rediscovered the hard way: appending a tenancy clause to an unbracketed OR list binds it to the last branch only, and the `hidden` attribute loses to any author rule that sets `display`, which rendered an empty chip with entirely correct markup. Also corrects a stale line in Tickets.md. It described the reply modal's To/Cc row as "matching the New Ticket modal's requester name / email row" — a row that no longer exists. Rather than deleting the sentence, it now says what changed and points at the new page, because the comparison is the sort of thing someone will go looking for. Linked from Tickets.md and the sidebar, beside the other ticket sub-pages.

    @edmozley edmozley committed Aug 12, 2026
    0941c2a
  • Bring the response document onto the wiki with the rest It was the only security write-up still living in the repository, at docs/security-review-2026-08.md — which is why the link Ed sent the reporter had /blob/ in it. There was never a round-one wiki page; the wiki's whole history only ever held the round-two and round-three pages. Two things in it had rotted since it was sent, and both are now fixed here and flagged in place rather than silently corrected: - it linked to security/findings-2026-08, a branch since merged and deleted - all eight commit ids in its table were pre-rebase and are orphaned; the table now carries the ids that exist on main, with a note that F5 sits out of sequence because it reached main as a cherry-pick The page opens by saying what it is — a record of what was said at the time, not a live document — and points at the round pages for the current position, because its own Outstanding section is now historical and mostly closed. Linked from Security.md, the round-two developer guide and the sidebar.

    @edmozley edmozley committed Aug 12, 2026
    439fa76
  • Correct the counts the added finding invalidated Ten findings, not nine; 185 checks, not 183; Outstanding is §10, not §8. Both openings now say why the tenth was nearly missed rather than quietly absorbing it into the total, since that is the part worth remembering.

    @edmozley edmozley committed Aug 12, 2026
    abbce5d
  • Correct the outstanding count, and add the guard that was missed Ed asked whether the Security page's summary — "two significant items remain open" — was accurate. It was not. Checking it turned up a third: a fail-open access guard in the task service, the last of the round-two list's "S3's cousins", now fixed in 980b0939. Both round-three pages gain the finding, the plain-English one as item 8 and the developer guide as 6b with the lock-wait reproduction. Security.md now lists the two genuinely open items separately from the two unscheduled features, rather than rolling everything into one sentence ending "and the rest" — which was the phrasing that let a miscount hide.

    @edmozley edmozley committed Aug 12, 2026
    de2479d
  • Document round three of the security work Two new pages for the nine findings deferred when round two merged, following the same pair as before: plain English for an operator, and a developer guide that gives each finding its risk, a reproducible proof of concept, the mitigation and why that mitigation actually closes it. The existing round-two pages now say what has been superseded and what has not, rather than leaving a reader to work out that their outstanding sections are partly historical. Security.md carries both new links, and the sidebar lists all four. Care taken to keep the same framing Erlend asked for: the outstanding sections lead, they name CSRF and lms/content as open in main today, and the round-three guide records where a mitigation is conditional rather than absolute — the CSP layer on the branding directory is skipped entirely without mod_headers, which was measured on a stock WAMP rather than assumed.

    @edmozley edmozley committed Aug 12, 2026
    98f9ef9
  • Mark the security work merged, and keep the open findings visible Erlend signed the branch off on 2026-08-12 and it has fast-forwarded into main at 86f8d257, so the "awaiting sign-off" and "not yet merged" banners on both hardening pages were wrong. His one substantive request was about framing rather than code: the deferred items must not read as closed. S2 is four live cross-tenant bugs reachable in main today, not a hardening backlog. Both pages now say that plainly, and the Security page — which described none of this and was the page an evaluator would actually land on — now carries the disclosure history and points at the outstanding list. Also corrects the default-admin bullet on Security: the published password is still admin/freeitsm, but since S5 the account is seeded with must_change_password so it cannot survive first sign-in, and Database Verification flags older installs still using it.

    @edmozley edmozley committed Aug 12, 2026
    9a1bee3
  • Security hardening 2026-08: add a troubleshooting section The page explained what changed and why, which is not the same as being able to fix a regression it caused. Five of these changes can now REFUSE something that previously worked, and in every case the symptom names the wrong culprit -- S3 in particular turns a database fault into what looks like a permissions problem. Adds: a symptom-to-cause table for the five tightening changes, the exact log strings to grep (all verified present in the code), the one genuinely new failure mode -- a secret that will not decrypt now reads as EMPTY rather than as visible ciphertext, so "the token is blank" means a key mismatch and not an unset setting -- and per-change revert notes. Two of them need care rather than a plain revert: reverting S5 does not clear a must_change_password already set, and reverting R3 restarts the media quarantining that was the whole complaint. S3 should not be reverted at all; if it is denying, the database fault it reports is the thing to fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    @edmozley edmozley committed Aug 12, 2026
    f700c75
  • Security hardening 2026-08: a user page and a developer guide The second round of the August security work -- the nine items Erlend Volden raised when he re-reviewed our fixes to his original report, plus four bugs in our own first-round code. Two pages, deliberately, because they have different readers: Security-Hardening-2026-08 answers four questions per item in ordinary language -- what was wrong, how it could have been used against you, what changed, and why that fixes it. Each item is labelled as a security flaw or as a feature we broke while fixing one, since both happened and the difference matters. Security-Hardening-2026-08-Developer-Guide is the same nine items with the code: file table, the before/after shapes, why the isMultiTenant() short-circuit in S1 is load-bearing, and what is deliberately still outstanding. Both carry a "not yet merged" notice, because the branch is still awaiting the reporter's sign-off and neither page describes current main. Every file path and wiki link in both pages was checked to resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    @edmozley edmozley committed Aug 11, 2026
    1109ad7
  • i18n Audit developer guide, including the script

    @edmozley edmozley committed Aug 9, 2026
    53e98c9
  • War Room: split into a user guide and a developer guide

    @edmozley edmozley committed Aug 9, 2026
    f16b9c7
  • War Room: Warbot, and why its hands and brain are separate

    @edmozley edmozley committed Aug 9, 2026
    bd46fe5
  • War Room: why being a fallback is not a licence to make it thin

    @edmozley edmozley committed Aug 9, 2026
    4983cd8
  • War Room: why a channel is a team, and why it depends on nothing

    @edmozley edmozley committed Aug 9, 2026
    39b8ecf
  • Techniques: three notes from the settings tidy-ups (#1006) - When you "normalise" a layout, state BOTH insets in one place. Leaving the modal header to LAYER 3's plain rule aligned three pages of four: inbox.css:2549 carries .modal-content:has(> .modal-body) > .modal-header at (0,3,0), which beats it, and only one modal has a .modal-body. Reads as a one-page quirk until you find the selector. - Measure where the TEXT starts, not where the box starts. Comparing a full-width header <div> to an inset <label> by rect.left measures the padding, not the misalignment — it reported a 14px error on four pages that were mostly fine. - A shared class means a one-page fix is a five-page fix. .tab-content is styled once in inbox.css at padding:30px — a sixth of a 360px screen — so trimming it for one module improved all five.

    @edmozley edmozley committed Aug 9, 2026
    d89cacb
  • Mobile: tickets settings (#1005) + five more techniques Mobile: Tickets gains a settings section; overview status/table/layer count updated. Five things promoted into Techniques: - Read a page's own CSS comments before opting it in. tickets/settings states outright that <body> must not be flex (extension-injected nodes became flex items and wrecked the layout) and builds its own shell. LAYER 2 would have re-created that bug. No harness catches it -- a headless run has no extensions. - A page that renders one tab at a time is 1/N verified. Measuring it as loaded reported clean; walking all 18 tabs found the real problems. - Exclude what must not change, don't enumerate what must. The anti-zoom rule listed six input types and missed a datetime-local. - Stacking a grid to one column does nothing if its children span two -- the browser creates an implicit second column and the rule looks like it failed. The tell is unequal tracks. - Ask the question measurement can answer. "Contained but crushed" kept slipping past overflow checks, so assert the actual property: nothing may still be laid out in more than one column at phone width.

    @edmozley edmozley committed Aug 9, 2026
    22f3d07
  • Mobile: Service Status round 2 (#1004) + two more techniques Module page gains a Round 2 section (two screens, tap-anywhere cards, pill alignment, the sticky action bar, the confirm) and a note that the settings card feed from #1000 is gone. Calendar page's card-feed bullet marked superseded, with the reasoning kept — it lost to consistency across the set, not to being wrong. Two things promoted into Techniques: - position:sticky does NOT pin anything to the bottom of a short container; it only stops an element scrolling away. Pair it with margin-top:auto in a flex column. And the two fight a negative margin-bottom — auto absorbs the free space the negative margin creates, so they cancel and the bar lands its own margin short. - Never mix getBoundingClientRect() with clientHeight on anything inside a transformed container. Rects are scaled, layout properties are not; mixing them produced a "bottom" beyond the viewport and sent a diagnosis completely wrong. Fourth false alarm from animating modals.

    @edmozley edmozley committed Aug 9, 2026
    6276664
  • Mobile: Service Status (#1003) + four more techniques New page Mobile-Friendly-Service-Status, linked from the overview (status blockquote, module table, honest assessment, layer count), the sidebar, all four sibling module pages, Techniques and Service-Status.md. Four things promoted into Techniques: - An inline style.display set by the page's own JS beats your rule. Service Status re-sets display:table on every render, so the card feed needed !important or it reverts on the first refresh. Grep a module for style.display before converting one of its tables. - "It reflows" is not the same as "it works". The board was already an auto-fill grid -- and a single column at 360px, i.e. a list. Ask what the component is FOR; a status board is for scanning, so density beats card size. - Not every module needs a mobile.js branch. Four in a row had one; this one shipped as CSS only. - A CSS comment edit is a code edit. Closing a comment early orphans text and the parser drops the rule that follows. Happened twice in one session, and brace-balance checks pass happily both times.

    @edmozley edmozley committed Aug 9, 2026
    26f0340
  • Mobile: Knowledge round 3 (#1002) + three widget-wrangling techniques Module page gains a "Round 3" section: the editor footer reaching the bottom edge, and full-screen text editing. Three things promoted into Techniques: - A JS-mounted widget injects its stylesheet at RUNTIME, so it beats yours on source order whatever the specificity. TinyMCE's skin lands after mobile.css; .tox-* rules need !important. The tell is a fix that looks like it HALF worked -- hiding the upsell badge succeeded while flex-wrap on the menubar did nothing, which read as "mostly fixed" until it was measured. Measure which half. - Reclaim a third-party widget's own chrome before adding your own. Four wrapped menubar rows plus an upsell badge were ~130px of a 720px screen; scrolling the menubar rather than hiding it returned 78px of typing area with every menu still reachable. - Build your own full screen, don't borrow the widget's. TinyMCE's fullscreen plugin was one line away but had no reachable exit in this init -- a phone user could get stuck. A fixed overlay with your own Close plus a pushState back-button exit cannot trap anyone.

    @edmozley edmozley committed Aug 9, 2026
    c1e2e44