@@ -4,7 +4,7 @@ Scores every webapp entry point on whether it could explain itself during an inc
44the ones worth fixing. An entry point is a Remix ` loader ` or ` action ` under
55` apps/webapp/app/routes ` , 427 of them at the time of writing.
66
7- The number it prints today is 19 out of 100. That is not a bug, and the rest of this file is mostly
7+ The number it prints today is 18 out of 100. That is not a bug, and the rest of this file is mostly
88about why you should believe it.
99
1010## Running it
@@ -20,7 +20,7 @@ suppresses. The single-route mode takes either the route path the report prints
2020the file name (` api.v1.token.ts ` ). An exact match wins over the routes it is a prefix of, and an
2121ambiguous prefix warns and names the alternatives rather than silently picking one.
2222
23- ## What 19 means
23+ ## What 18 means
2424
2525It is the mean score of the 412 entry points that had at least one applicable check, where an
2626entry's score is the share of its applicable checks that passed. It is low because the webapp does
@@ -30,9 +30,18 @@ route and the request id and nothing about whose request it was.
3030
3131The score was 76 until we stopped crediting routes for the error handling they do not do. Emptying
3232every catch clause in the tree used to score it 100, which meant the metric paid you for deleting
33- error handling. Now removing the catches takes 19 down to 8, and removing the logs as well takes it
34- to 2. If you change this package, keep that property: mutate the tree to remove error handling and
35- check the score falls.
33+ error handling.
34+
35+ Two invariants hold now, and both are asserted in ` test/score.test.ts ` rather than measured once:
36+
37+ - ** Removing error handling must not raise the score.** Deleting every catch clause takes 18 to 8,
38+ and deleting the logs as well takes it to 2.
39+ - ** Adding error handling that does nothing must not raise the score.** Wrapping every body in
40+ ` try { ... } catch (e) { throw e } ` leaves it at 18, with no entry moving in either direction.
41+ That mutation used to be worth 27 points across the tree, because a rethrow-only clause counted
42+ as a pass while no catch at all was not-applicable, and the two are observationally identical.
43+
44+ If you change this package, check both directions still hold.
3645
3746So the number is deliberately unflattering, and one platform change would move most of it. Nothing
3847central attaches a tenant: ` logger ` pushes ` { requestId, path, host, method } ` onto every line
@@ -43,8 +52,9 @@ rather than celebrating.
4352
4453## The four checks
4554
46- - ** error-classification** : does every catch clause decide what it caught, by rethrowing, by
47- branching on the error, or by guarding a parse it can answer for.
55+ - ** error-classification** : does every catch clause decide what it caught, by branching on the
56+ error or by guarding a parse it can answer for. A clause that only rethrows decides nothing and
57+ is read as though there were no catch, so it neither passes nor fails.
4858- ** auth-boundary** : does a route handling credentials, tokens, billing or impersonation check who
4959 is asking.
5060- ** request-context** : when this entry point's failure is reported, is the tenant named.
@@ -62,6 +72,11 @@ are reported as a figure: the `AUDIT` and `CONTEXT` lines. 333 entry points fail
6272fails ` request-context ` * and* something else keeps both findings and stays in the list, so
6373` /account/tokens ` still shows the whole picture.
6474
75+ 18 of those 333 are sensitive, including ` /admin/impersonate ` , the API-key regeneration route and
76+ four envvars routes, so the ` CONTEXT ` line says how many. Read them out of
77+ ` observability-map.json ` , where every entry keeps its full check results, rather than assuming the
78+ list is the whole story.
79+
6580` request-context ` is still scored, unlike ` audit-trail ` . The gap it measures is real and the score
6681is meant to show it. Only the presentation collapses.
6782
@@ -94,11 +109,17 @@ support.
94109## Suppression
95110
96111``` ts
97- // obs-map-disable-next-line auth-boundary -- public by design, see ADR 12
112+ // obs-map-disable auth-boundary -- public by design, see ADR 12
98113```
99114
100115The reason is mandatory: a suppression without one is ignored. The directive is read from comments
101- only, line by line, so a string literal quoting it does not switch a check off.
116+ only, so a string literal quoting it does not switch a check off.
117+
118+ It applies to the whole entry point, not to the line under it. It was called
119+ ` obs-map-disable-next-line ` , which was untrue in a way that mattered: a directive on the last line
120+ of a file switched a check off for everything above it. Genuine line scoping is not available,
121+ because a finding is attached to an entry point and carries no line number to match against, so the
122+ name was corrected instead. The old spelling is not honoured, and there is a test saying so.
102123
103124A suppression cannot raise a score. The suppressed check leaves the numerator and the denominator,
104125and the result is capped by what the entry would have scored unsuppressed, so suppressing a failing
@@ -112,11 +133,16 @@ Read these before trusting a specific verdict.
112133
113134- ** One hop, same file only.** If a loader delegates to a helper in the same file, that helper's
114135 statements, catches and calls count as the route's. A helper's own helpers do not, and nothing
115- imported from another module is ever opened. Most of what a route does is behind an import, which
116- is why ` auth-boundary ` applies to 26 entry points rather than 427.
136+ imported from another module is ever opened. ` auth-boundary ` applies to 23 entry points: it gates
137+ on sensitivity first, which is 26 routes, and the one-hop limit accounts for the other 3, which
138+ hand their work to an imported helper and are reported as unverified rather than unguarded.
117139- ** Loggers are matched by spelling.** A call counts as logging when the callee reads ` logger.* ` or
118140 ` log.* ` . An aliased logger, one wrapped in a helper, or ` console.error ` is invisible, so a route
119141 can be reported as recording nothing while it records plenty.
142+ - ** A catch that logs and rethrows reads as though it only rethrows.** The clause evidence cannot
143+ say whether a clause does anything besides rethrow, so ` error-classification ` withholds credit
144+ rather than granting it. Crediting it would reopen the free-points path a single ` logger.error `
145+ line wide.
120146- ** Only the first object-literal argument is read** for identifier fields, and only its property
121147 names. ` logger.error("failed", ctx) ` where ` ctx ` is a variable contributes nothing, and neither
122148 does a second object.
0 commit comments