nilometer 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/LICENSE +21 -0
- package/README.md +462 -2
- package/core/pricing/prices.json +348 -0
- package/core/schema/001_ingestion.sql +57 -0
- package/core/schema/002_line_count.sql +7 -0
- package/core/schema/003_derived.sql +70 -0
- package/core/schema/004_requests_dedup.sql +50 -0
- package/core/schema/005_repositories.sql +25 -0
- package/core/schema/006_status_readings.sql +33 -0
- package/core/schema/007_pricing.sql +95 -0
- package/core/schema/008_interruptions.sql +263 -0
- package/core/schema/009_headroom.sql +84 -0
- package/core/schema/010_attribution.sql +106 -0
- package/core/schema/011_projections.sql +83 -0
- package/core/schema/012_unsplit_cache_writes.sql +49 -0
- package/core/schema/013_repository_revalidation.sql +104 -0
- package/core/schema/014_observed_readings.sql +204 -0
- package/core/schema/015_response_observations.sql +127 -0
- package/core/schema/016_windows_repository_paths.sql +41 -0
- package/core/schema/017_unattributed_usage_once.sql +47 -0
- package/core/schema/018_request_time_index.sql +21 -0
- package/core/schema/019_dedup_earliest_of_equals.sql +61 -0
- package/core/schema/020_limit_quota_fields.sql +131 -0
- package/core/schema/021_plan_periods.sql +121 -0
- package/core/schema/022_attachment_walk.sql +223 -0
- package/core/schema/023_auto_continuations.sql +129 -0
- package/core/schema/024_counted_limit_groups.sql +29 -0
- package/core/schema/025_model_fallback.sql +77 -0
- package/core/schema/026_changed_readings.sql +106 -0
- package/core/schema/027_weekly_crossings.sql +32 -0
- package/core/schema/028_stale_readings.sql +196 -0
- package/core/schema/029_lockout_sessions.sql +68 -0
- package/core/schema/030_repository_aliases.sql +81 -0
- package/core/schema/031_cte_cursor_budget.sql +153 -0
- package/dist/cli/main.cli.js +39 -0
- package/dist/cli/program.js +801 -0
- package/dist/core/db/database.js +182 -0
- package/dist/core/db/fingerprint.js +71 -0
- package/dist/core/ingest/attribution.js +131 -0
- package/dist/core/ingest/classify.js +382 -0
- package/dist/core/ingest/command.js +95 -0
- package/dist/core/ingest/derive.js +212 -0
- package/dist/core/ingest/discover.js +126 -0
- package/dist/core/ingest/ingest.js +160 -0
- package/dist/core/ingest/reader.js +89 -0
- package/dist/core/ingest/reset-time.js +97 -0
- package/dist/core/ingest/results.js +201 -0
- package/dist/core/ingest/spool.js +165 -0
- package/dist/core/ingest/store.js +99 -0
- package/dist/core/install/install.js +185 -0
- package/dist/core/install/locations.js +89 -0
- package/dist/core/install/private-files.js +259 -0
- package/dist/core/install/record.js +103 -0
- package/dist/core/plans/plan-prices.js +128 -0
- package/dist/core/plans/repository-aliases.js +136 -0
- package/dist/core/pricing/prices.js +147 -0
- package/dist/core/settings/settings-file.js +215 -0
- package/dist/core/settings/statusline.js +240 -0
- package/dist/core/util/commands.js +91 -0
- package/dist/core/util/node-version.js +58 -0
- package/dist/core/util/open-page.js +69 -0
- package/dist/core/util/paths.js +36 -0
- package/dist/core/verify/compare.js +77 -0
- package/dist/core/verify/verify.js +220 -0
- package/dist/viewer/explain.js +574 -0
- package/dist/viewer/format.js +150 -0
- package/dist/viewer/html-charts.js +1292 -0
- package/dist/viewer/html-contract.js +2 -0
- package/dist/viewer/html-data.js +718 -0
- package/dist/viewer/html.js +1654 -0
- package/dist/viewer/queries.js +128 -0
- package/dist/viewer/render.js +602 -0
- package/dist/viewer/report.js +120 -0
- package/dist/viewer/save.js +110 -0
- package/docs/requirements.md +113 -0
- package/hooks/statusline.sh +104 -0
- package/package.json +61 -4
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
-- 020_limit_quota_fields: a limit hit's window and reset come from quotaLimits when Claude Code writes it (D-067).
|
|
2
|
+
--
|
|
3
|
+
-- D-004 read the window and reset only from the message text, because that was the only place
|
|
4
|
+
-- they existed (Claude Code 2.1.214). By 2.1.281 every limit-hit line also carries a structured
|
|
5
|
+
-- quotaLimits object with rateLimitType and resetsAt (observed 2026-09-24; they agreed with the
|
|
6
|
+
-- text on every hit examined). The text parse stays for older lines, and events.window,
|
|
7
|
+
-- events.reset_text, and events.reset_at_utc keep meaning "from the text". The structured values
|
|
8
|
+
-- get their own columns, the views prefer them, and each logged hit says which source its window
|
|
9
|
+
-- and reset came from. Where the two disagree, ingestion records a line problem (report, don't
|
|
10
|
+
-- fix): 'quota_window_disagrees' and 'quota_reset_disagrees'; a member present but unusable is
|
|
11
|
+
-- 'unusable_quota_field'. line_problems is rebuilt to admit those codes, because SQLite can't
|
|
12
|
+
-- change a CHECK constraint in place; it's derived data, and the parser version bump that ships
|
|
13
|
+
-- with this migration re-derives it anyway.
|
|
14
|
+
|
|
15
|
+
ALTER TABLE events ADD COLUMN quota_window TEXT CHECK (quota_window IN ('five_hour', 'seven_day')); -- source: quotaLimits.rateLimitType when it's 'five_hour' or 'seven_day' (limit hits only, D-067)
|
|
16
|
+
ALTER TABLE events ADD COLUMN quota_resets_at_utc TEXT; -- source: quotaLimits.resetsAt (Unix seconds, whole, 1..253402300799) as ISO-8601 UTC (limit hits only, D-067)
|
|
17
|
+
|
|
18
|
+
CREATE TABLE line_problems_020 (
|
|
19
|
+
id INTEGER PRIMARY KEY, -- derived: row identity
|
|
20
|
+
raw_line_id INTEGER NOT NULL REFERENCES raw_lines (id), -- derived: the line with the problem
|
|
21
|
+
problem TEXT NOT NULL, -- derived: 'missing_field', 'unparsed_timestamp', 'non_message_iteration', 'unusable_quota_field', 'quota_window_disagrees', or 'quota_reset_disagrees'
|
|
22
|
+
detail TEXT, -- derived: the field name, raw timestamp JSON, iteration type, quotaLimits member, or both disagreeing values
|
|
23
|
+
CHECK (problem IN ('missing_field', 'unparsed_timestamp', 'non_message_iteration', 'unusable_quota_field', 'quota_window_disagrees', 'quota_reset_disagrees'))
|
|
24
|
+
) STRICT;
|
|
25
|
+
INSERT INTO line_problems_020 (id, raw_line_id, problem, detail)
|
|
26
|
+
SELECT id, raw_line_id, problem, detail FROM line_problems;
|
|
27
|
+
DROP TABLE line_problems;
|
|
28
|
+
ALTER TABLE line_problems_020 RENAME TO line_problems;
|
|
29
|
+
CREATE INDEX line_problems_line ON line_problems (raw_line_id);
|
|
30
|
+
|
|
31
|
+
DROP VIEW logged_limit_hits;
|
|
32
|
+
|
|
33
|
+
-- Observed: logged limit hits, one per distinct line (copies of a uuid in one session count once), with the D-021 position.
|
|
34
|
+
-- Window and reset prefer quotaLimits over the message text (D-067), and say which one they came from.
|
|
35
|
+
CREATE VIEW logged_limit_hits AS
|
|
36
|
+
WITH copies AS (
|
|
37
|
+
SELECT
|
|
38
|
+
p.raw_line_id, p.session_id, p.uuid, p.parent_uuid, p.timestamp_utc, p.is_sidechain,
|
|
39
|
+
e.window AS text_window, e.quota_window, e.reset_text,
|
|
40
|
+
e.reset_at_utc AS text_reset_at_utc, e.quota_resets_at_utc,
|
|
41
|
+
ROW_NUMBER() OVER (
|
|
42
|
+
-- A line without a uuid is its own group: NULL uuids must not merge with each other.
|
|
43
|
+
PARTITION BY p.session_id, p.uuid, CASE WHEN p.uuid IS NULL THEN p.raw_line_id END
|
|
44
|
+
ORDER BY f.root, f.relative_path, l.first_run_id, l.line_number
|
|
45
|
+
) AS copy_rank
|
|
46
|
+
FROM events e
|
|
47
|
+
JOIN parsed_lines p ON p.raw_line_id = e.raw_line_id
|
|
48
|
+
JOIN raw_lines l ON l.id = e.raw_line_id
|
|
49
|
+
JOIN source_files f ON f.id = l.source_file_id
|
|
50
|
+
WHERE e.class = 'limit_hit'
|
|
51
|
+
)
|
|
52
|
+
SELECT
|
|
53
|
+
c.raw_line_id, c.session_id, c.uuid, c.parent_uuid, c.timestamp_utc, c.is_sidechain,
|
|
54
|
+
-- The structured field is exact and doesn't depend on wording; the text is the fallback for
|
|
55
|
+
-- lines written before Claude Code added quotaLimits.
|
|
56
|
+
COALESCE(c.quota_window, c.text_window) AS window,
|
|
57
|
+
c.reset_text,
|
|
58
|
+
COALESCE(c.quota_resets_at_utc, c.text_reset_at_utc) AS reset_at_utc,
|
|
59
|
+
COALESCE((
|
|
60
|
+
SELECT CASE
|
|
61
|
+
WHEN par.user_content = 'tool_result' THEN 'mid_task'
|
|
62
|
+
WHEN par.user_content = 'prompt' AND par.is_meta = 0 THEN 'turn_start'
|
|
63
|
+
ELSE 'unknown'
|
|
64
|
+
END
|
|
65
|
+
FROM parsed_lines par
|
|
66
|
+
JOIN raw_lines pl ON pl.id = par.raw_line_id
|
|
67
|
+
JOIN source_files pf ON pf.id = pl.source_file_id
|
|
68
|
+
WHERE par.session_id = c.session_id AND par.uuid = c.parent_uuid
|
|
69
|
+
ORDER BY pf.root, pf.relative_path, pl.first_run_id, pl.line_number
|
|
70
|
+
LIMIT 1
|
|
71
|
+
), 'unknown') AS position,
|
|
72
|
+
CASE WHEN c.quota_window IS NOT NULL THEN 'log_field'
|
|
73
|
+
WHEN c.text_window IS NOT NULL THEN 'log_text' END AS window_source,
|
|
74
|
+
CASE WHEN c.quota_resets_at_utc IS NOT NULL THEN 'log_field'
|
|
75
|
+
WHEN c.text_reset_at_utc IS NOT NULL THEN 'log_text' END AS reset_source
|
|
76
|
+
FROM copies c
|
|
77
|
+
WHERE c.copy_rank = 1;
|
|
78
|
+
|
|
79
|
+
DROP VIEW obs_limit_hits_events;
|
|
80
|
+
|
|
81
|
+
-- Observed: every rate-limit interruption, logged hits merged with status line groups without double-counting (D-004, D-023).
|
|
82
|
+
CREATE VIEW obs_limit_hits_events AS
|
|
83
|
+
WITH logged AS (
|
|
84
|
+
SELECT
|
|
85
|
+
h.*,
|
|
86
|
+
(SELECT g.raw_line_id FROM status_limit_groups g
|
|
87
|
+
WHERE (h.window IS NULL OR h.window = g.window)
|
|
88
|
+
AND unixepoch(h.timestamp_utc, 'subsec') > g.resets_at - g.window_seconds
|
|
89
|
+
AND unixepoch(h.timestamp_utc, 'subsec') <= g.resets_at
|
|
90
|
+
ORDER BY g.resets_at, g.window
|
|
91
|
+
LIMIT 1) AS status_raw_line_id
|
|
92
|
+
FROM logged_limit_hits h
|
|
93
|
+
),
|
|
94
|
+
merged AS (
|
|
95
|
+
SELECT
|
|
96
|
+
'session_log' AS source,
|
|
97
|
+
l.raw_line_id,
|
|
98
|
+
l.status_raw_line_id,
|
|
99
|
+
l.session_id,
|
|
100
|
+
l.timestamp_utc AS hit_at_utc,
|
|
101
|
+
COALESCE(l.window, g.window) AS window,
|
|
102
|
+
-- The payload's resets_at is exact; so is quotaLimits.resetsAt, and reset text has minute precision.
|
|
103
|
+
COALESCE(g.reset_at_utc, l.reset_at_utc) AS reset_at_utc,
|
|
104
|
+
CASE WHEN g.raw_line_id IS NOT NULL THEN 'status_line' ELSE l.reset_source END AS reset_source,
|
|
105
|
+
l.reset_text,
|
|
106
|
+
l.position,
|
|
107
|
+
l.is_sidechain
|
|
108
|
+
FROM logged l
|
|
109
|
+
LEFT JOIN status_limit_groups g ON g.raw_line_id = l.status_raw_line_id
|
|
110
|
+
UNION ALL
|
|
111
|
+
SELECT 'status_line', g.raw_line_id, g.raw_line_id, g.session_id, g.first_at_utc, g.window,
|
|
112
|
+
g.reset_at_utc, 'status_line', NULL, 'unknown', 0
|
|
113
|
+
FROM status_limit_groups g
|
|
114
|
+
WHERE NOT EXISTS (SELECT 1 FROM logged l WHERE l.status_raw_line_id = g.raw_line_id)
|
|
115
|
+
)
|
|
116
|
+
SELECT
|
|
117
|
+
m.*,
|
|
118
|
+
(round(unixepoch(m.reset_at_utc, 'subsec') * 1000) - round(unixepoch(m.hit_at_utc, 'subsec') * 1000)) / 1000.0 AS hit_to_reset_seconds,
|
|
119
|
+
(SELECT MIN(d.timestamp_utc) FROM requests_dedup d
|
|
120
|
+
WHERE d.session_id = m.session_id AND d.timestamp_utc > m.hit_at_utc) AS next_session_request_at_utc,
|
|
121
|
+
(SELECT p.timestamp_utc FROM parsed_lines p
|
|
122
|
+
WHERE p.session_id = m.session_id AND p.user_content = 'prompt' AND p.is_meta = 0
|
|
123
|
+
AND p.timestamp_utc > m.hit_at_utc
|
|
124
|
+
ORDER BY p.timestamp_utc, p.raw_line_id LIMIT 1) AS next_session_prompt_at_utc,
|
|
125
|
+
-- origin.kind as written (D-022): never relabeled as automatic or manual.
|
|
126
|
+
(SELECT p.origin_kind FROM parsed_lines p
|
|
127
|
+
WHERE p.session_id = m.session_id AND p.user_content = 'prompt' AND p.is_meta = 0
|
|
128
|
+
AND p.timestamp_utc > m.hit_at_utc
|
|
129
|
+
ORDER BY p.timestamp_utc, p.raw_line_id LIMIT 1) AS next_session_prompt_origin_kind,
|
|
130
|
+
m.reset_at_utc > (SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS reset_after_coverage
|
|
131
|
+
FROM merged m;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
-- 021_plan_periods: limit hits, lockouts, and elapsed lockout time per plan period (D-075, step G2.2).
|
|
2
|
+
--
|
|
3
|
+
-- The interruption counts span the whole log history (D-004), so after a plan change one count
|
|
4
|
+
-- mixed two plans: a hit on the plan before an upgrade counted beside hits on the plan after it.
|
|
5
|
+
-- The plan in effect comes from what the user entered with plan-price set (D-027): a plan applies
|
|
6
|
+
-- from the first day of its entered month, in the process's local zone, until the next entry's.
|
|
7
|
+
--
|
|
8
|
+
-- Observed views may not read plan_prices, which holds prices (structure.test.ts). plan_periods
|
|
9
|
+
-- reads only its names and months, and the structure test lets an observed view reach plan_prices
|
|
10
|
+
-- through plan_periods alone, and fails if plan_periods ever reads a price. The counts stay
|
|
11
|
+
-- observations: each hit and lockout is an event, and the plan is a label the user supplied.
|
|
12
|
+
|
|
13
|
+
-- Helper: each entered plan's months, from its effective month until the next entry's (D-027, D-075).
|
|
14
|
+
CREATE VIEW plan_periods AS
|
|
15
|
+
SELECT
|
|
16
|
+
effective_month AS from_month, -- source: plan_prices.effective_month, the first local month the plan applies
|
|
17
|
+
-- derived: the next entry's month, where this plan stops applying (exclusive); NULL while it applies.
|
|
18
|
+
LEAD(effective_month) OVER (ORDER BY effective_month) AS until_month,
|
|
19
|
+
plan_name -- source: plan_prices.plan_name, as the user wrote it
|
|
20
|
+
FROM plan_prices;
|
|
21
|
+
|
|
22
|
+
-- Observed: each limit hit, with the plan period its local month falls in (D-075).
|
|
23
|
+
CREATE VIEW obs_limit_hits_by_plan_events AS
|
|
24
|
+
SELECT
|
|
25
|
+
e.raw_line_id,
|
|
26
|
+
e.session_id,
|
|
27
|
+
e.hit_at_utc,
|
|
28
|
+
e.window,
|
|
29
|
+
e.source,
|
|
30
|
+
-- 'plan' inside an entered plan's months, 'none' before the first entry (or with none at all),
|
|
31
|
+
-- and 'unknown' for a hit with no time, which no month can place.
|
|
32
|
+
CASE
|
|
33
|
+
WHEN e.hit_at_utc IS NULL THEN 'unknown'
|
|
34
|
+
WHEN p.from_month IS NULL THEN 'none'
|
|
35
|
+
ELSE 'plan'
|
|
36
|
+
END AS period_kind,
|
|
37
|
+
p.from_month,
|
|
38
|
+
p.until_month,
|
|
39
|
+
p.plan_name
|
|
40
|
+
FROM obs_limit_hits_events e
|
|
41
|
+
-- Periods never overlap, so a hit matches at most one; months compare as YYYY-MM text.
|
|
42
|
+
LEFT JOIN plan_periods p
|
|
43
|
+
ON e.hit_at_utc IS NOT NULL
|
|
44
|
+
AND strftime('%Y-%m', e.hit_at_utc, 'localtime') >= p.from_month
|
|
45
|
+
AND (p.until_month IS NULL OR strftime('%Y-%m', e.hit_at_utc, 'localtime') < p.until_month);
|
|
46
|
+
|
|
47
|
+
-- Observed: each lockout interval, with the plan period its first hit falls in (D-075).
|
|
48
|
+
-- An interval crossing a period boundary belongs to the period where it began, so it is never
|
|
49
|
+
-- split or counted twice.
|
|
50
|
+
CREATE VIEW obs_lockout_by_plan_events AS
|
|
51
|
+
SELECT
|
|
52
|
+
i.*,
|
|
53
|
+
CASE WHEN p.from_month IS NULL THEN 'none' ELSE 'plan' END AS period_kind,
|
|
54
|
+
p.from_month,
|
|
55
|
+
p.until_month,
|
|
56
|
+
p.plan_name
|
|
57
|
+
FROM obs_lockout_intervals i
|
|
58
|
+
LEFT JOIN plan_periods p
|
|
59
|
+
ON strftime('%Y-%m', i.locked_from_utc, 'localtime') >= p.from_month
|
|
60
|
+
AND (p.until_month IS NULL OR strftime('%Y-%m', i.locked_from_utc, 'localtime') < p.until_month);
|
|
61
|
+
|
|
62
|
+
-- Observed: limit hits, lockouts, and elapsed lockout time per plan period, each with its own span (D-075).
|
|
63
|
+
CREATE VIEW obs_interruptions_by_plan AS
|
|
64
|
+
WITH periods AS (
|
|
65
|
+
SELECT 'plan' AS period_kind, from_month, until_month, plan_name FROM plan_periods
|
|
66
|
+
UNION ALL
|
|
67
|
+
-- Before the first entered plan; with no plan entered, the whole history.
|
|
68
|
+
SELECT 'none', NULL, (SELECT MIN(from_month) FROM plan_periods), NULL
|
|
69
|
+
UNION ALL
|
|
70
|
+
-- Hits with no time: counted, so the rows add up to obs_limit_hits, but in no period.
|
|
71
|
+
SELECT 'unknown', NULL, NULL, NULL
|
|
72
|
+
),
|
|
73
|
+
-- Materialized: the hit and lockout views are among the costliest on real data (D-059), and every
|
|
74
|
+
-- period reads them.
|
|
75
|
+
hits AS MATERIALIZED (
|
|
76
|
+
SELECT period_kind, from_month, window, hit_at_utc FROM obs_limit_hits_by_plan_events
|
|
77
|
+
),
|
|
78
|
+
locks AS MATERIALIZED (
|
|
79
|
+
SELECT period_kind, from_month, lockout_seconds FROM obs_lockout_by_plan_events
|
|
80
|
+
),
|
|
81
|
+
-- What a period's span is drawn from: request times and hit times, each with its local month.
|
|
82
|
+
dated AS MATERIALIZED (
|
|
83
|
+
SELECT timestamp_utc AS at_utc, strftime('%Y-%m', timestamp_utc, 'localtime') AS month
|
|
84
|
+
FROM requests_dedup WHERE timestamp_utc IS NOT NULL
|
|
85
|
+
UNION ALL
|
|
86
|
+
SELECT hit_at_utc, strftime('%Y-%m', hit_at_utc, 'localtime') FROM hits WHERE hit_at_utc IS NOT NULL
|
|
87
|
+
),
|
|
88
|
+
counted AS (
|
|
89
|
+
SELECT
|
|
90
|
+
p.period_kind,
|
|
91
|
+
p.from_month,
|
|
92
|
+
-- The last month the plan applies, for display: the month before until_month.
|
|
93
|
+
CASE WHEN p.until_month IS NOT NULL
|
|
94
|
+
THEN strftime('%Y-%m', p.until_month || '-01', '-1 month') END AS to_month,
|
|
95
|
+
p.until_month,
|
|
96
|
+
p.plan_name,
|
|
97
|
+
(SELECT COUNT(*) FROM hits h WHERE h.period_kind = p.period_kind AND h.from_month IS p.from_month) AS hits,
|
|
98
|
+
(SELECT COUNT(*) FROM hits h WHERE h.period_kind = p.period_kind AND h.from_month IS p.from_month
|
|
99
|
+
AND h.window = 'five_hour') AS five_hour_hits,
|
|
100
|
+
(SELECT COUNT(*) FROM hits h WHERE h.period_kind = p.period_kind AND h.from_month IS p.from_month
|
|
101
|
+
AND h.window = 'seven_day') AS seven_day_hits,
|
|
102
|
+
(SELECT COUNT(*) FROM hits h WHERE h.period_kind = p.period_kind AND h.from_month IS p.from_month
|
|
103
|
+
AND h.window IS NULL) AS unknown_window_hits,
|
|
104
|
+
(SELECT COUNT(*) FROM locks l WHERE l.period_kind = p.period_kind AND l.from_month IS p.from_month) AS lockouts,
|
|
105
|
+
-- TOTAL, as obs_lockout_time totals intervals: 0 over none.
|
|
106
|
+
(SELECT TOTAL(l.lockout_seconds) FROM locks l
|
|
107
|
+
WHERE l.period_kind = p.period_kind AND l.from_month IS p.from_month) AS lockout_seconds,
|
|
108
|
+
-- The span the period's data covers, from what is in it (principle 5); none for 'unknown'.
|
|
109
|
+
(SELECT MIN(d.at_utc) FROM dated d WHERE p.period_kind <> 'unknown'
|
|
110
|
+
AND (p.from_month IS NULL OR d.month >= p.from_month)
|
|
111
|
+
AND (p.until_month IS NULL OR d.month < p.until_month)) AS covers_from,
|
|
112
|
+
(SELECT MAX(d.at_utc) FROM dated d WHERE p.period_kind <> 'unknown'
|
|
113
|
+
AND (p.from_month IS NULL OR d.month >= p.from_month)
|
|
114
|
+
AND (p.until_month IS NULL OR d.month < p.until_month)) AS covers_to
|
|
115
|
+
FROM periods p
|
|
116
|
+
)
|
|
117
|
+
SELECT *
|
|
118
|
+
FROM counted
|
|
119
|
+
-- A period appears when it holds a hit or any dated data; an entry for a month with no data, or
|
|
120
|
+
-- a plan entered for the future, has nothing to report.
|
|
121
|
+
WHERE hits > 0 OR covers_from IS NOT NULL;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
-- 022_attachment_walk: a limit hit's position looks past attachment lines of known metadata types (D-076, step G2.3).
|
|
2
|
+
--
|
|
3
|
+
-- D-021 decides a hit's position by its direct parent: a tool result means the model was inside
|
|
4
|
+
-- its own loop (mid-task), a non-meta prompt means a turn had just started, anything else is
|
|
5
|
+
-- unknown. Newer Claude Code versions write attachment lines between a request and what it
|
|
6
|
+
-- answered: token reminders, environment and memory context, hook output, and the like. By the
|
|
7
|
+
-- end of Phase 8 the direct parent of 24 of 31 hits was such a line, so "unknown" held most hits.
|
|
8
|
+
--
|
|
9
|
+
-- The walk follows a hit's parent chain past attachment lines whose type is classified as
|
|
10
|
+
-- metadata Claude Code adds by itself, and decides the position by D-021's rule at the first line
|
|
11
|
+
-- that isn't one. attachment_walk lists every type seen in real logs on 2026-10-13, classified by
|
|
12
|
+
-- its keys only (no values). A type not listed stops the walk, so a type a future Claude Code
|
|
13
|
+
-- adds can only leave a hit unknown; it can never move one to mid-task.
|
|
14
|
+
|
|
15
|
+
-- Reference: which attachment types the position walk passes (D-076). Seeded here, not derived.
|
|
16
|
+
CREATE TABLE attachment_walk (
|
|
17
|
+
attachment_type TEXT PRIMARY KEY, -- source: attachment.type, as written in session logs (classified by structure, D-076)
|
|
18
|
+
rule TEXT NOT NULL, -- source: D-076: 'pass' walks past it; 'by_fields' passes it only when its own fields say it's automatic
|
|
19
|
+
CHECK (rule IN ('pass', 'by_fields'))
|
|
20
|
+
) STRICT;
|
|
21
|
+
|
|
22
|
+
INSERT INTO attachment_walk (attachment_type, rule) VALUES
|
|
23
|
+
-- Reminders Claude Code adds.
|
|
24
|
+
('total_tokens_reminder', 'pass'),
|
|
25
|
+
('batching_reminder_sent', 'pass'),
|
|
26
|
+
('silent_turn_reminder', 'pass'),
|
|
27
|
+
('todo_reminder', 'pass'),
|
|
28
|
+
('task_reminder', 'pass'),
|
|
29
|
+
('plan_mode', 'pass'),
|
|
30
|
+
-- Session context.
|
|
31
|
+
('environment', 'pass'),
|
|
32
|
+
('date', 'pass'),
|
|
33
|
+
('date_change', 'pass'),
|
|
34
|
+
('model', 'pass'),
|
|
35
|
+
('session_context', 'pass'),
|
|
36
|
+
('instructions', 'pass'),
|
|
37
|
+
('nested_memory', 'pass'),
|
|
38
|
+
('credential_org', 'pass'),
|
|
39
|
+
('auto_mode', 'pass'),
|
|
40
|
+
('command_permissions', 'pass'),
|
|
41
|
+
('remote_session_change', 'pass'),
|
|
42
|
+
('prompt_snapshot', 'pass'),
|
|
43
|
+
-- Tool and skill listings.
|
|
44
|
+
('deferred_tools_record', 'pass'),
|
|
45
|
+
('deferred_tools_delta', 'pass'),
|
|
46
|
+
('mcp_instructions_delta', 'pass'),
|
|
47
|
+
('skill_listing', 'pass'),
|
|
48
|
+
('agent_listing_delta', 'pass'),
|
|
49
|
+
('invoked_skills', 'pass'),
|
|
50
|
+
-- Notes on a tool call; the walk continues to the tool result they annotate.
|
|
51
|
+
('hook_success', 'pass'),
|
|
52
|
+
('hook_system_message', 'pass'),
|
|
53
|
+
('hook_additional_context', 'pass'),
|
|
54
|
+
('bash_output_audience_note', 'pass'),
|
|
55
|
+
('read_truncation_notice', 'pass'),
|
|
56
|
+
('structured_output', 'pass'),
|
|
57
|
+
-- Notes on files and thinking.
|
|
58
|
+
('edited_text_file', 'pass'),
|
|
59
|
+
('compact_file_reference', 'pass'),
|
|
60
|
+
('thinking_drop', 'pass'),
|
|
61
|
+
('thinking_stripped', 'pass'),
|
|
62
|
+
-- Carried with a prompt or a tool result; the walk continues to the line they belong to.
|
|
63
|
+
('file', 'pass'),
|
|
64
|
+
('plan_mode_exit', 'pass'),
|
|
65
|
+
-- A message delivered into a running loop: automatic when it's a task notification (247 of 271
|
|
66
|
+
-- on real data); a human or peer message is input, so it stops the walk.
|
|
67
|
+
('queued_command', 'by_fields');
|
|
68
|
+
|
|
69
|
+
DROP VIEW logged_limit_hits;
|
|
70
|
+
|
|
71
|
+
-- Observed: logged limit hits, one per distinct line (copies of a uuid in one session count once), with the
|
|
72
|
+
-- D-021 position decided past metadata attachment lines (D-076). Window and reset prefer quotaLimits (D-067).
|
|
73
|
+
CREATE VIEW logged_limit_hits AS
|
|
74
|
+
WITH RECURSIVE copies AS (
|
|
75
|
+
SELECT
|
|
76
|
+
p.raw_line_id, p.session_id, p.uuid, p.parent_uuid, p.timestamp_utc, p.is_sidechain,
|
|
77
|
+
e.window AS text_window, e.quota_window, e.reset_text,
|
|
78
|
+
e.reset_at_utc AS text_reset_at_utc, e.quota_resets_at_utc,
|
|
79
|
+
ROW_NUMBER() OVER (
|
|
80
|
+
-- A line without a uuid is its own group: NULL uuids must not merge with each other.
|
|
81
|
+
PARTITION BY p.session_id, p.uuid, CASE WHEN p.uuid IS NULL THEN p.raw_line_id END
|
|
82
|
+
ORDER BY f.root, f.relative_path, l.first_run_id, l.line_number
|
|
83
|
+
) AS copy_rank
|
|
84
|
+
FROM events e
|
|
85
|
+
JOIN parsed_lines p ON p.raw_line_id = e.raw_line_id
|
|
86
|
+
JOIN raw_lines l ON l.id = e.raw_line_id
|
|
87
|
+
JOIN source_files f ON f.id = l.source_file_id
|
|
88
|
+
WHERE e.class = 'limit_hit'
|
|
89
|
+
),
|
|
90
|
+
firsts AS (
|
|
91
|
+
SELECT * FROM copies WHERE copy_rank = 1
|
|
92
|
+
),
|
|
93
|
+
-- Each hit's parent chain. A step finds its line as D-021 did: the first copy, in canonical
|
|
94
|
+
-- order, of the parent uuid in the hit's session. The walk takes another step only past an
|
|
95
|
+
-- attachment that attachment_walk passes; 50 steps is far past any chain seen (8), and stops a
|
|
96
|
+
-- loop of uuids, which nothing guarantees can't occur.
|
|
97
|
+
walk (raw_line_id, session_id, line_id, passed) AS (
|
|
98
|
+
SELECT
|
|
99
|
+
h.raw_line_id,
|
|
100
|
+
h.session_id,
|
|
101
|
+
(SELECT par.raw_line_id FROM parsed_lines par
|
|
102
|
+
JOIN raw_lines pl ON pl.id = par.raw_line_id
|
|
103
|
+
JOIN source_files pf ON pf.id = pl.source_file_id
|
|
104
|
+
WHERE par.session_id = h.session_id AND par.uuid = h.parent_uuid
|
|
105
|
+
ORDER BY pf.root, pf.relative_path, pl.first_run_id, pl.line_number
|
|
106
|
+
LIMIT 1),
|
|
107
|
+
0
|
|
108
|
+
FROM firsts h
|
|
109
|
+
UNION ALL
|
|
110
|
+
SELECT
|
|
111
|
+
w.raw_line_id,
|
|
112
|
+
w.session_id,
|
|
113
|
+
(SELECT par.raw_line_id FROM parsed_lines par
|
|
114
|
+
JOIN raw_lines pl ON pl.id = par.raw_line_id
|
|
115
|
+
JOIN source_files pf ON pf.id = pl.source_file_id
|
|
116
|
+
WHERE par.session_id = w.session_id AND par.uuid = cur.parent_uuid
|
|
117
|
+
ORDER BY pf.root, pf.relative_path, pl.first_run_id, pl.line_number
|
|
118
|
+
LIMIT 1),
|
|
119
|
+
w.passed + 1
|
|
120
|
+
FROM walk w
|
|
121
|
+
JOIN parsed_lines cur ON cur.raw_line_id = w.line_id
|
|
122
|
+
JOIN raw_lines cb ON cb.id = cur.raw_line_id
|
|
123
|
+
-- The attachment's type is read from its raw bytes: parsed_lines doesn't keep it, and only the
|
|
124
|
+
-- few lines on a hit's chain are read this way.
|
|
125
|
+
JOIN attachment_walk aw
|
|
126
|
+
ON aw.attachment_type = json_extract(CAST(cb.bytes AS TEXT), '$.attachment.type')
|
|
127
|
+
WHERE cur.type = 'attachment'
|
|
128
|
+
AND (aw.rule = 'pass'
|
|
129
|
+
OR json_extract(CAST(cb.bytes AS TEXT), '$.attachment.commandMode') = 'task-notification')
|
|
130
|
+
AND w.passed < 50
|
|
131
|
+
),
|
|
132
|
+
-- Where each walk stopped: its last step.
|
|
133
|
+
ends AS (
|
|
134
|
+
SELECT raw_line_id, line_id, passed
|
|
135
|
+
FROM (
|
|
136
|
+
SELECT w.*, ROW_NUMBER() OVER (PARTITION BY w.raw_line_id ORDER BY w.passed DESC) AS step_rank
|
|
137
|
+
FROM walk w
|
|
138
|
+
)
|
|
139
|
+
WHERE step_rank = 1
|
|
140
|
+
)
|
|
141
|
+
SELECT
|
|
142
|
+
c.raw_line_id, c.session_id, c.uuid, c.parent_uuid, c.timestamp_utc, c.is_sidechain,
|
|
143
|
+
-- The structured field is exact and doesn't depend on wording; the text is the fallback for
|
|
144
|
+
-- lines written before Claude Code added quotaLimits.
|
|
145
|
+
COALESCE(c.quota_window, c.text_window) AS window,
|
|
146
|
+
c.reset_text,
|
|
147
|
+
COALESCE(c.quota_resets_at_utc, c.text_reset_at_utc) AS reset_at_utc,
|
|
148
|
+
-- D-021's rule, at the line the walk stopped on. A missing parent, a meta line, another type, or
|
|
149
|
+
-- an attachment the walk doesn't pass all leave the position unknown.
|
|
150
|
+
COALESCE((
|
|
151
|
+
SELECT CASE
|
|
152
|
+
WHEN par.user_content = 'tool_result' THEN 'mid_task'
|
|
153
|
+
WHEN par.user_content = 'prompt' AND par.is_meta = 0 THEN 'turn_start'
|
|
154
|
+
ELSE 'unknown'
|
|
155
|
+
END
|
|
156
|
+
FROM parsed_lines par
|
|
157
|
+
WHERE par.raw_line_id = e.line_id
|
|
158
|
+
), 'unknown') AS position,
|
|
159
|
+
-- How many attachment lines the walk passed to decide it (D-076): 0 is D-021's direct parent.
|
|
160
|
+
e.passed AS attachments_passed,
|
|
161
|
+
CASE WHEN c.quota_window IS NOT NULL THEN 'log_field'
|
|
162
|
+
WHEN c.text_window IS NOT NULL THEN 'log_text' END AS window_source,
|
|
163
|
+
CASE WHEN c.quota_resets_at_utc IS NOT NULL THEN 'log_field'
|
|
164
|
+
WHEN c.text_reset_at_utc IS NOT NULL THEN 'log_text' END AS reset_source
|
|
165
|
+
FROM firsts c
|
|
166
|
+
JOIN ends e ON e.raw_line_id = c.raw_line_id;
|
|
167
|
+
|
|
168
|
+
DROP VIEW obs_limit_hits_events;
|
|
169
|
+
|
|
170
|
+
-- Observed: every rate-limit interruption, logged hits merged with status line groups without double-counting
|
|
171
|
+
-- (D-004, D-023), each with the attachment lines its position looked past (D-076).
|
|
172
|
+
CREATE VIEW obs_limit_hits_events AS
|
|
173
|
+
WITH logged AS (
|
|
174
|
+
SELECT
|
|
175
|
+
h.*,
|
|
176
|
+
(SELECT g.raw_line_id FROM status_limit_groups g
|
|
177
|
+
WHERE (h.window IS NULL OR h.window = g.window)
|
|
178
|
+
AND unixepoch(h.timestamp_utc, 'subsec') > g.resets_at - g.window_seconds
|
|
179
|
+
AND unixepoch(h.timestamp_utc, 'subsec') <= g.resets_at
|
|
180
|
+
ORDER BY g.resets_at, g.window
|
|
181
|
+
LIMIT 1) AS status_raw_line_id
|
|
182
|
+
FROM logged_limit_hits h
|
|
183
|
+
),
|
|
184
|
+
merged AS (
|
|
185
|
+
SELECT
|
|
186
|
+
'session_log' AS source,
|
|
187
|
+
l.raw_line_id,
|
|
188
|
+
l.status_raw_line_id,
|
|
189
|
+
l.session_id,
|
|
190
|
+
l.timestamp_utc AS hit_at_utc,
|
|
191
|
+
COALESCE(l.window, g.window) AS window,
|
|
192
|
+
-- The payload's resets_at is exact; so is quotaLimits.resetsAt, and reset text has minute precision.
|
|
193
|
+
COALESCE(g.reset_at_utc, l.reset_at_utc) AS reset_at_utc,
|
|
194
|
+
CASE WHEN g.raw_line_id IS NOT NULL THEN 'status_line' ELSE l.reset_source END AS reset_source,
|
|
195
|
+
l.reset_text,
|
|
196
|
+
l.position,
|
|
197
|
+
l.is_sidechain,
|
|
198
|
+
l.attachments_passed
|
|
199
|
+
FROM logged l
|
|
200
|
+
LEFT JOIN status_limit_groups g ON g.raw_line_id = l.status_raw_line_id
|
|
201
|
+
UNION ALL
|
|
202
|
+
-- A hit seen only in the status line has no parent to walk: unknown, past nothing.
|
|
203
|
+
SELECT 'status_line', g.raw_line_id, g.raw_line_id, g.session_id, g.first_at_utc, g.window,
|
|
204
|
+
g.reset_at_utc, 'status_line', NULL, 'unknown', 0, 0
|
|
205
|
+
FROM status_limit_groups g
|
|
206
|
+
WHERE NOT EXISTS (SELECT 1 FROM logged l WHERE l.status_raw_line_id = g.raw_line_id)
|
|
207
|
+
)
|
|
208
|
+
SELECT
|
|
209
|
+
m.*,
|
|
210
|
+
(round(unixepoch(m.reset_at_utc, 'subsec') * 1000) - round(unixepoch(m.hit_at_utc, 'subsec') * 1000)) / 1000.0 AS hit_to_reset_seconds,
|
|
211
|
+
(SELECT MIN(d.timestamp_utc) FROM requests_dedup d
|
|
212
|
+
WHERE d.session_id = m.session_id AND d.timestamp_utc > m.hit_at_utc) AS next_session_request_at_utc,
|
|
213
|
+
(SELECT p.timestamp_utc FROM parsed_lines p
|
|
214
|
+
WHERE p.session_id = m.session_id AND p.user_content = 'prompt' AND p.is_meta = 0
|
|
215
|
+
AND p.timestamp_utc > m.hit_at_utc
|
|
216
|
+
ORDER BY p.timestamp_utc, p.raw_line_id LIMIT 1) AS next_session_prompt_at_utc,
|
|
217
|
+
-- origin.kind as written (D-022): never relabeled as automatic or manual.
|
|
218
|
+
(SELECT p.origin_kind FROM parsed_lines p
|
|
219
|
+
WHERE p.session_id = m.session_id AND p.user_content = 'prompt' AND p.is_meta = 0
|
|
220
|
+
AND p.timestamp_utc > m.hit_at_utc
|
|
221
|
+
ORDER BY p.timestamp_utc, p.raw_line_id LIMIT 1) AS next_session_prompt_origin_kind,
|
|
222
|
+
m.reset_at_utc > (SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS reset_after_coverage
|
|
223
|
+
FROM merged m;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
-- 023_auto_continuations: Claude Code's own resumption after a lockout is shown where it was logged (D-077, step G2.4).
|
|
2
|
+
--
|
|
3
|
+
-- After a limit's window resets, Claude Code continues a stopped session by itself and marks it:
|
|
4
|
+
-- a user line with isMeta true and origin.kind "auto-continuation" (D-022, observed from 2.1.281).
|
|
5
|
+
-- The report said only "reset to next Claude Code request", which doesn't say who continued.
|
|
6
|
+
-- Only the logged marker earns the label; nothing is inferred from timing (D-022), so a workflow
|
|
7
|
+
-- restarting its subagents, which writes no marker, stays unlabeled.
|
|
8
|
+
--
|
|
9
|
+
-- A marker belongs to the latest logged limit hit before it in its own session, and through that
|
|
10
|
+
-- hit to the hit's lockout interval (D-023). A marker with no earlier hit in its session, or whose
|
|
11
|
+
-- hit has no known reset and so no interval, follows no lockout and isn't counted.
|
|
12
|
+
--
|
|
13
|
+
-- Cost: the lockout views are read by several metrics, and the merged hit views behind them are
|
|
14
|
+
-- the costliest on real data (D-059). So the markers and their hits are found from the raw lines
|
|
15
|
+
-- (auto_continuation_marks), and each view computes interval membership once.
|
|
16
|
+
|
|
17
|
+
-- Helper: each logged auto-continuation, once per line, with the latest logged limit hit before it in its session (D-077).
|
|
18
|
+
CREATE VIEW auto_continuation_marks AS
|
|
19
|
+
WITH marks AS (
|
|
20
|
+
SELECT
|
|
21
|
+
p.raw_line_id, p.session_id, p.timestamp_utc,
|
|
22
|
+
-- Copies of one line (a uuid written twice in a session) count once, as limit hits do.
|
|
23
|
+
ROW_NUMBER() OVER (
|
|
24
|
+
PARTITION BY p.session_id, p.uuid, CASE WHEN p.uuid IS NULL THEN p.raw_line_id END
|
|
25
|
+
ORDER BY f.root, f.relative_path, l.first_run_id, l.line_number
|
|
26
|
+
) AS copy_rank
|
|
27
|
+
FROM parsed_lines p
|
|
28
|
+
JOIN raw_lines l ON l.id = p.raw_line_id
|
|
29
|
+
JOIN source_files f ON f.id = l.source_file_id
|
|
30
|
+
WHERE p.origin_kind = 'auto-continuation' AND p.is_meta = 1 AND p.timestamp_utc IS NOT NULL
|
|
31
|
+
),
|
|
32
|
+
-- The logged limit-hit lines, one per distinct line, chosen as logged_limit_hits chooses them.
|
|
33
|
+
hit_lines AS MATERIALIZED (
|
|
34
|
+
SELECT raw_line_id, session_id, timestamp_utc
|
|
35
|
+
FROM (
|
|
36
|
+
SELECT
|
|
37
|
+
p.raw_line_id, p.session_id, p.timestamp_utc,
|
|
38
|
+
ROW_NUMBER() OVER (
|
|
39
|
+
PARTITION BY p.session_id, p.uuid, CASE WHEN p.uuid IS NULL THEN p.raw_line_id END
|
|
40
|
+
ORDER BY f.root, f.relative_path, l.first_run_id, l.line_number
|
|
41
|
+
) AS copy_rank
|
|
42
|
+
FROM events e
|
|
43
|
+
JOIN parsed_lines p ON p.raw_line_id = e.raw_line_id
|
|
44
|
+
JOIN raw_lines l ON l.id = e.raw_line_id
|
|
45
|
+
JOIN source_files f ON f.id = l.source_file_id
|
|
46
|
+
WHERE e.class = 'limit_hit'
|
|
47
|
+
)
|
|
48
|
+
WHERE copy_rank = 1
|
|
49
|
+
)
|
|
50
|
+
SELECT
|
|
51
|
+
m.raw_line_id, -- source: the marker line
|
|
52
|
+
m.session_id, -- source: its sessionId
|
|
53
|
+
m.timestamp_utc AS continued_at_utc, -- source: its timestamp
|
|
54
|
+
(SELECT h.raw_line_id FROM hit_lines h
|
|
55
|
+
WHERE h.session_id = m.session_id AND h.timestamp_utc < m.timestamp_utc
|
|
56
|
+
ORDER BY h.timestamp_utc DESC, h.raw_line_id DESC
|
|
57
|
+
LIMIT 1) AS hit_raw_line_id -- derived: the latest logged hit before it in the session
|
|
58
|
+
FROM marks m
|
|
59
|
+
WHERE m.copy_rank = 1;
|
|
60
|
+
|
|
61
|
+
DROP VIEW obs_lockout_intervals;
|
|
62
|
+
|
|
63
|
+
-- Observed: merged lockout intervals, hit → reset, with the first Claude Code request at or after each reset (D-023),
|
|
64
|
+
-- and the sessions Claude Code itself resumed after it, as logged (D-077).
|
|
65
|
+
CREATE VIEW obs_lockout_intervals AS
|
|
66
|
+
WITH interval_hits AS MATERIALIZED (
|
|
67
|
+
-- Computed once for everything below: interval membership is the costly part.
|
|
68
|
+
SELECT raw_line_id, start_utc, end_utc, interval_number FROM obs_lockout_interval_hits
|
|
69
|
+
),
|
|
70
|
+
grouped AS (
|
|
71
|
+
SELECT
|
|
72
|
+
interval_number,
|
|
73
|
+
MIN(start_utc) AS locked_from_utc,
|
|
74
|
+
MAX(end_utc) AS locked_until_utc,
|
|
75
|
+
COUNT(*) AS hits
|
|
76
|
+
FROM interval_hits
|
|
77
|
+
GROUP BY interval_number
|
|
78
|
+
),
|
|
79
|
+
with_next AS (
|
|
80
|
+
SELECT
|
|
81
|
+
g.*,
|
|
82
|
+
(SELECT MIN(d.timestamp_utc) FROM requests_dedup d WHERE d.timestamp_utc >= g.locked_until_utc) AS next_request_at_utc
|
|
83
|
+
FROM grouped g
|
|
84
|
+
),
|
|
85
|
+
resumed AS (
|
|
86
|
+
SELECT
|
|
87
|
+
h.interval_number,
|
|
88
|
+
COUNT(*) AS auto_continuations,
|
|
89
|
+
MIN((round(unixepoch(m.continued_at_utc, 'subsec') * 1000) - round(unixepoch(g.locked_until_utc, 'subsec') * 1000)) / 1000.0)
|
|
90
|
+
AS first_seconds
|
|
91
|
+
FROM auto_continuation_marks m
|
|
92
|
+
JOIN interval_hits h ON h.raw_line_id = m.hit_raw_line_id
|
|
93
|
+
JOIN grouped g ON g.interval_number = h.interval_number
|
|
94
|
+
GROUP BY h.interval_number
|
|
95
|
+
)
|
|
96
|
+
SELECT
|
|
97
|
+
w.*,
|
|
98
|
+
(round(unixepoch(w.locked_until_utc, 'subsec') * 1000) - round(unixepoch(w.locked_from_utc, 'subsec') * 1000)) / 1000.0 AS lockout_seconds,
|
|
99
|
+
(round(unixepoch(w.next_request_at_utc, 'subsec') * 1000) - round(unixepoch(w.locked_until_utc, 'subsec') * 1000)) / 1000.0 AS reset_to_next_request_seconds,
|
|
100
|
+
w.locked_until_utc > (SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS reset_after_coverage,
|
|
101
|
+
-- How many sessions Claude Code resumed by itself after this reset, as logged; 0 when none.
|
|
102
|
+
COALESCE(r.auto_continuations, 0) AS auto_continuations,
|
|
103
|
+
-- The earliest of those, seconds after the reset; NULL when none.
|
|
104
|
+
r.first_seconds AS first_auto_continuation_seconds
|
|
105
|
+
FROM with_next w
|
|
106
|
+
LEFT JOIN resumed r ON r.interval_number = w.interval_number;
|
|
107
|
+
|
|
108
|
+
-- Observed: each logged auto-continuation that followed a lockout, with its interval and how long after the reset (D-077).
|
|
109
|
+
CREATE VIEW obs_auto_continuation_events AS
|
|
110
|
+
WITH interval_hits AS MATERIALIZED (
|
|
111
|
+
SELECT raw_line_id, end_utc, interval_number FROM obs_lockout_interval_hits
|
|
112
|
+
),
|
|
113
|
+
resets AS (
|
|
114
|
+
-- Each interval's reset as obs_lockout_intervals takes it: the latest end of its hits' spans.
|
|
115
|
+
SELECT interval_number, MAX(end_utc) AS locked_until_utc FROM interval_hits GROUP BY interval_number
|
|
116
|
+
)
|
|
117
|
+
SELECT
|
|
118
|
+
m.raw_line_id,
|
|
119
|
+
m.session_id,
|
|
120
|
+
m.continued_at_utc,
|
|
121
|
+
m.hit_raw_line_id,
|
|
122
|
+
h.interval_number,
|
|
123
|
+
r.locked_until_utc,
|
|
124
|
+
-- As written: a marker logged before the reset would show a negative time (none observed).
|
|
125
|
+
(round(unixepoch(m.continued_at_utc, 'subsec') * 1000) - round(unixepoch(r.locked_until_utc, 'subsec') * 1000)) / 1000.0
|
|
126
|
+
AS seconds_after_reset
|
|
127
|
+
FROM auto_continuation_marks m
|
|
128
|
+
JOIN interval_hits h ON h.raw_line_id = m.hit_raw_line_id
|
|
129
|
+
JOIN resets r ON r.interval_number = h.interval_number;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- 024_counted_limit_groups: a status-line-only limit hit starts from a counted reading (D-078, step G2.6).
|
|
2
|
+
--
|
|
3
|
+
-- status_limit_groups took every reading at or over 100%, including readings observed after their
|
|
4
|
+
-- own window's reset (the payload lag D-024 describes: a session's reading repeats its last
|
|
5
|
+
-- response until it makes another request). A group whose first at-limit reading came after the
|
|
6
|
+
-- reset drew a lockout of 0 s, from a hit to a reset it had already passed. Every other window
|
|
7
|
+
-- metric counts only readings before the reset (after_reset = 0); this view now does too.
|
|
8
|
+
|
|
9
|
+
DROP VIEW status_limit_groups;
|
|
10
|
+
|
|
11
|
+
-- Observed: status line windows observed at 100%, one row per (window, resets_at), from init onward, counting only
|
|
12
|
+
-- readings before the window's reset (D-023, D-044, D-078).
|
|
13
|
+
CREATE VIEW status_limit_groups AS
|
|
14
|
+
WITH at_limit AS (
|
|
15
|
+
SELECT
|
|
16
|
+
r.raw_line_id, r.window, r.resets_at, r.session_id, r.observed_at_utc,
|
|
17
|
+
ROW_NUMBER() OVER (PARTITION BY r.window, r.resets_at ORDER BY r.observed_at_s, r.raw_line_id) AS reading_rank,
|
|
18
|
+
COUNT(*) OVER (PARTITION BY r.window, r.resets_at) AS readings_at_limit
|
|
19
|
+
FROM window_readings r
|
|
20
|
+
-- A reading after its reset belongs to no window (D-024), so it can't show that window at its limit.
|
|
21
|
+
WHERE r.used_percentage >= 100 AND r.after_reset = 0
|
|
22
|
+
)
|
|
23
|
+
SELECT
|
|
24
|
+
raw_line_id, window, resets_at, session_id, readings_at_limit,
|
|
25
|
+
observed_at_utc AS first_at_utc,
|
|
26
|
+
strftime('%Y-%m-%dT%H:%M:%fZ', resets_at, 'unixepoch') AS reset_at_utc,
|
|
27
|
+
CASE window WHEN 'five_hour' THEN 5 * 3600 ELSE 7 * 86400 END AS window_seconds
|
|
28
|
+
FROM at_limit
|
|
29
|
+
WHERE reading_rank = 1;
|