Person picker (approved prototype):
- The chart page header uses the chat's two-line chip; the chip, the
points pill, the chart toggle and 「那一刻的天空」 share
`--chat-header-pill`, now 44px at every width.
- The popup is a 380px card: 「选择星盘」 with 「共 N 人」, rows of at
least 60px (avatar, name + badge over the date line, the check on the
selected row only, then ⋯), the selected row an accent wash with no
box, a divider and 「+ 新增一个人」 in the accent. The list scrolls
only on overflow. At ≤640px the same body opens as a bottom sheet
(Drawer, grab handle, 64px rows, 44px ⋯). Choose, edit, delete
confirm and every status are unchanged.
盘面检视:
- Nothing picked: six facts (宫主, 第 1 宫里的星, 看上升的星, 上升八分点,
当前运期, 此刻月亮), a 九曜一览 3×3 grid whose cells pick a body, and
「问 AI:我的上升」 / 「从太阳开始看」.
- A house: adds 宫主大运 and a 「和这一宫有关的星」 grid marking the
occupants, the lord and the aspecting bodies; 「看宫主X」 picks the lord.
- The frame stays identical (the grid slot is empty for a planet or a
point); measured in headless Chrome it fits every tier with no inner
scroll. The chat panel shows compact cells.
Skeleton: while the view loads, the chart page renders the finished
page's own workspace, grid and cards at their fixed heights (titles as
text, soft pulsing blocks, still under reduced motion), with the board's
宫位依次点亮 drawing. Card boxes match the loaded page at 1280 and 390.
On a phone the chart page's top bar now keeps 52px once the page's own
birth line arrives.
BUG-1511: the person chip was hidden in phone headers by the static-note
rule's higher specificity; the override now outranks it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Recurrence of BUG-1040 / BUG-1104, reproduced in a production build:
- Warm decision now lasts until Home commits (releaseHomeWarmStart), not
one microtask; the navigation note is peeked during render and taken
after commit. React discarding and repeating Home's first render no
longer flips it to the cold path.
- Secondary-page prefetch starts as soon as the list settles and on
pointer intent; a 新建对话 click while it is in flight waits for it
(at most 2 s, aria-busy) instead of cold-starting beside it.
- The session-list provider remembers its settled list on unmount, so
returning from /learn starts warm; /learn header links use AppLink.
- Lifecycle tests repeat Home's first render inside a transition; 14
new path tests (a)-(e), 12 fail on the old code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
深读 on the chart page drops its tabs, the 「深读」 header and the 640px
inner-scroll card. Three cards flow with the page: 时运流年 → 能量图谱 →
出生资料与口径. Graphics come first and the numbers sit behind 「看…」
controls (a link on desktop, a 44px button on a phone):
- 时运流年: 逐级放大 bars (大运 → 分运 → 子运, pale trapezoid
connectors, today marker), a 星程 age ruler (title in one constant),
and 九曜行运 as twelve house cells that select the house on the board,
with the Sade Sati pill. Period tables, the second dasha (Chara now
requested only when opened), the transit table and 叠到盘面 are
behind expanders.
- 能量图谱: one bar per planet with a threshold tick and strong /
medium / weak bands; twelve house bars with a dashed mean line.
Shadbala parts, rupas and house values are behind an expander.
- 出生资料与口径: pills plus 「展开说明」 on desktop, a one-line summary row
on a phone; the grid, boundary sentence and birth calendar unfold.
Colours come from theme tokens via color-mix: a pale accent wash marks
"current", planets are ink rather than rainbow colours, and dark mode
follows.
BUG-1510: when /api/bhava_bala failed alone, the ashtakavarga layer
arrived without houses, the section rendered an empty note, and the hook
cached the layer as loaded, so nothing could refetch it. The section now
shows a skeleton while waiting, and copy plus 重试 when the houses are
missing or the layer failed. An ashtakavarga layer without houses stays
re-requestable. The staging root cause is unconfirmed; a shared
per-IP API rate limit is suspected and tracked separately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Reports send vedastro_evidence_budget=report. The API defers like consult
(no sync main-entry overview, cache-only gate, gateway in the foreground
pool) with a 25 s deadline from session start (cap 45 s). On overrun the
report continues with VedAstro official_blocked /
report_vedastro_deadline_exceeded and the failure is stored in the day's
negative snapshot cache. The main-entry overview attach now fails soft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
recover_expired_personal_report_jobs now releases the reservation (by the
report's request_id, idempotent with the worker and the delete trigger)
before it fails a job; a release that raises or errors leaves that row
running for the next sweep and logs errorCode=report_refund_failed.
Migration 20261102010000_report_lease_expiry_release.sql replaces only the
function (invoker, empty search_path, service_role only).
The report worker no longer closes a failed job when its refund fails: it
logs report_refund_failed and leaves the lease to the recovery sweep.
Rectification retries a failed release once with the same case request id
and logs rectification_refund_failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
The birth place picker reported null on a failed timezone lookup and on a
half-chosen level; BirthLocationFields then cleared the place and the picker
reset its draft. Editing a saved person's date, time or place during a
timezone outage silently erased the birthplace with no error or retry.
The same path reset the picker when changing province/city/country on edit
and when opening an overseas person while the region list loaded.
- The picker only reports a freshly resolved place; failures and unfinished
choices keep the previous value and report "pending" instead.
- BirthPlacePendingScope + BirthPlaceSubmitButton disable 保存 (edit dialogs)
and 确定 (onboarding) while pending.
- Failure copy for a person with a place: 「时区这会儿没查到,原来的出生地还在,
查到后才能保存。」 with 重试; new profiles keep the existing copy.
- Track the moment the stored offset was resolved for, so going back to the
original date after a successful lookup re-resolves instead of saving a
stale offset; ignore late results from aborted lookups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Code-and-stub audit instead of a live drill (no staging SSH). Per-dependency
tables of what the user sees, charge/refund, logs/alerts and evidence, plus
gaps ranked by risk. Four clear bugs recorded as investigating: BUG-1504
(VedAstro truncated read -> 500), BUG-1505 (report lease expiry keeps
credits), BUG-1506 (profile edit wipes place on timezone failure),
BUG-1507 (legacy China profiles silently +8).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
_compute_shadbala called swe.julday with no swe in scope, so any request with
year/month/day/lat/lon raised NameError. The endpoint now lives in
scripts/shadbala_service/api.py, which imports Swiss Ephemeris itself; the
handler only forwards. _shadbala_rule_variants and
_compute_shadbala_advanced_layer moved with it (handler methods 217 -> 215,
baseline lowered). New real-HTTP regression test with ruff F821 on the server,
added to the quick gate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Classifies the 16 production-pending migrations added since the 10-14 list
(A 1, B 3, C 7, D 5) with what production users see and what the rehearsal
should check. The one user-visible rewrite is the first-question-free
backfill: accounts that already completed a real question count as used,
confirmed by the product owner on 2026-11-01.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Publish job 11273 and its rerun 11284 timed out fetching from
registry.npmjs.org (zwitch, then zustand) while every validate check passed.
The gate's own install already uses registry.npmmirror.com; the image's npm ci
now does too. The lock file pins versions and integrity hashes, so the
installed packages are unchanged. Approved by the product owner 2026-11-01.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Gate run 6343 refused the invoker v3 on agentic_rectification_cases: since
20260814010000 service_role has no direct rights on cases, every case write
goes through owner-held security-definer RPCs. v3 now only checks the person,
calls v2 and hands the case to agentic_rectification_bind_case_subject_v1,
a service_role-only security-definer function that locks the case, binds a new
case and its conversation to the person, and checks the person matches.
Migration parsed with pglast (58 statements, 17 PL/pgSQL bodies).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
The staging gate's real-database tests refused open_agentic_rectification_case_v3:
it was security definer, and 20260814010000 revokes EXECUTE on the 11-argument
v2 even from its owner, leaving service_role alone. v3 is now security invoker,
like the 12-argument v2; reading the person and labelling the chat session move
into two service_role-only security-definer helpers, because service_role has
no rights on chart_profiles or chat_sessions. The migration has never run, so it
is corrected in place.
Also adds the annual.full price rows to the whole-table pricing assertion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
Staging's backend gate (run 6338) failed three budgets: handler method
count 218 > 217, JyotishAPIHandler.__new__ sites 6 > 5, and the scripts/
root .py count. The package and facts code now live in
scripts/annual_service/ (package.py, report_facts.py, api.py with
compute_annual_package / compute_annual_report_facts and
AnnualServiceInputError). jyotish_api_server.py registers /api/annual
mode=package, /api/annual/package and /api/annual/report_facts thinly
through a module-level _annual_service helper, like /api/chart_points,
with no handler methods: back to 217. Tests call the module functions
and keep a thin-registration test; no cap or baseline changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
/reports/annual opens the annual report directly. A translucent overlay
shows the range, 「某人的这一年」, the three notes, the birth-time line
(self links to rectification), the short 现状 form, 「开始写年运」 and the
price line; then sections stream in as they are saved, and a year
already written opens without the overlay. 「我的报告」 gets the annual
card. Docs: design note with every default pending astrologer review,
DESIGN.md, VOICE.md, CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
POST /api/annual with mode=package (or /api/annual/package) returns one
versioned package built from the existing producers: year charts with
ascendant/lord, Muntha, Panchadhikari year lord, annual strength, Sahams,
Tajika yogas, Tripataki, Mudda, native Patyayini, Tajika monthly charts;
natal Vimshottari MD/AD/PD and Narayana (legacy + Rath); slow-planet
ingresses, stations, retrograde loops and natal-point crossings; Tithi
Pravesha as an independent view. Report range, year-chart validity and each
period system's own year are separate, every boundary is an exact instant
with timezone, and every block carries computed/blocked/not_applicable with
a reason. The default /api/annual response is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
calc_solar_return_chart hard-coded the mean node for the birth and annual
charts, so node_mode=true never reached the annual Rahu/Ketu. The default
stays mean; cmd_solar_return and cmd_tajika now pass the request's mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
The G2 precheck and the G5 noise floor had only a results file; the script
that produces them is now in scripts/research/d60_two_way_g2_g5.py, with
a test that reproduces the committed JSON byte for byte.
G5 is now reported per switch, with the ayanamsa offset in arcminutes.
Against the Raman baseline all three alternatives flip 77/77. Between the
alternatives themselves the number follows the offset: Lahiri to KP 21/77
(27.3%, 5.81'), KP to True Chitra 19/77 (24.7%, 5.02'), Lahiri to True
Chitra 2/77 (2.6%, 0.83'). The report says so and no longer claims that
any frame change flips every chart; the registered gate is baseline
relative and still triggers.
The report also states that G2's "not evaluable" comes from the registered
same-day answer policy, not from a measurement that D60 does not help.
Co-Authored-By: Grok (agent loop)
The G2-G5 results came without the script that produced them, so G5's
77/77 ayanamsa flip cannot be replayed. Commit the script, report G5 per
alternative ayanamsa, and say that G2's zero questions came from the
same-day scoring rule the planner chose.
Co-Authored-By: Claude (agent loop) <noreply@anthropic.com>
The registered same-day answer policy needs an event dated on the birth day;
the v5 holdout has none in any of the 77 cases, so group B adds no question
and is identical to group A. G2 is therefore not evaluable by construction,
not a measured zero, and no placebo test has a lift to check.
G5 is measurable and decisive: switching the ayanamsa from Raman to Lahiri,
KP or True Chitra changes the ascendant D60 sign in 77 of 77 cases (100%,
threshold 20%). Under the registered stop rule any minute-level wording is
forbidden.
Recommendation: do not adopt. D60 stays reference-only and the external copy
does not claim seconds. BUG-1497 is closed_by_design; the mapping fix itself
is BUG-1500 and already merged.
Co-Authored-By: Grok (agent loop)
The production D60 branch added one sign to 1-based even signs, with no
source, while the repository's own guide and the vedastro mapping count
from the sign itself. PyJHora agrees with the latter: 33/77 recorded times
before, 77/77 after.
- scripts/varga.py: D60 is (si+pi)%12 for odd and even signs; other
mappings and the vedastro mode are untouched.
- tests/test_d60_varga_mapping.py: 12 signs x 60 parts against
PyJHora, plus the even-sign first part.
- scripts/research/d60_two_way_run.py: the G1 reconciliation that found
this, now in the repository with a test; G1 passes 77/77.
- scripts/varga.py is part of the extended frozen identity, so the sealed
holdout and the reported-offset sweep were re-frozen per ERR-110. All
20 and 900 trials match the upstream_sync9b records on every field and
the metrics and summary are unchanged.
Co-Authored-By: Grok (agent loop)
Product chose 1: fix production D60 (count from the sign itself for odd
and even signs) before continuing the two-way study. Renumber the
branch's D60 bug BUG-1498 -> BUG-1500 to clear the staging collision,
and settle the D60 boundary scoring rule as same-day events only.
Co-Authored-By: Claude (agent loop) <noreply@anthropic.com>
Classification only needs the question, history, name and session model,
so it now starts in beforeReserve once moderation passes and is awaited
after the question is written. Billing is unchanged: smalltalk is still
reserved and refunded by complete_consultation_free, and an unused early
call (reserve refused) is capped at the classifier's 3 s fail-open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Product chose A after the A/B: low cut answer-step reasoning tokens ~45%
and answer time 34s -> 22s with no quality loss. Unset now means "low";
CONSULT_ANSWER_REASONING_EFFORT=off restores the pre-change request body,
and the old "unset keeps the body" guarantee moves to "off".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The D60 ascendant of the production classical_local mapping agrees with
PyJHora on 33 of 77 recorded times (42.9%). All 44 mismatches are 1-based
even signs and differ by exactly one sign; the repository's own vedastro
mode agrees with PyJHora on 77 of 77.
So G1 fails the 95% gate, but the cause is the production mapping rule in
scripts/varga.py, not a missing D60 signal. BUG-1498 records the
inconsistency as investigating. G2-G5 did not run and no scoring rule was
registered.
jyotishganit is not installed here and VedAstro was not called, so the
reconciliation is single-engine and says so.
A task left running wrote its outcome into the next test's cache directory,
so the next test saw a remembered failure and never called the gateway.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staging showed 「记下了:2016-09 2016 年 9 月上大学。」. evidenceRecapLine only
recognised an unspaced 「2016年」 as the phrase's own date, so a spaced year or
an ISO / slash date got the label in front again. It now recognises
`\d{4}\s*年` and `\d{4}[-/.]\d{1,2}`; phrases without a date still get the
label. Regression test fails before the fix and passes after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Environment checks are done: swisseph 2.10.03, PyJHora importable with a
shashtyamsa chart, the v5 holdout present, and the four baseline test
files green (98 passed). jyotishganit is not installed, so G1 ends up with
PyJHora as the only external engine.
The B-group scoring rule cannot be implemented as written: the six-question
replay answers date-level boundary questions, while a D60 change happens
every couple of minutes inside one day. Two workable options are recorded
in PROGRESS and posted to the issue; nothing is scored before the rule is
preregistered.
Nadi/D150 seconds were falsified in BUG-1240; this brief only tests D60 as a
discriminator: production V9 vs V9 + D60 segment boundaries on the 77-case v5
holdout, both directions (help and harm), preregistered gates G1-G5, plus a
20-case model-in-the-loop conversation comparison. Offline, no production or
frozen-file changes. Reserves BUG-1497.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7f7321b77725c8798292f184fc038fcfaf8c11ff)
The sync9b edit replaced the two sync8 entries in
IDENTITY_FORBIDDEN_PREFIXES instead of appending after them, so the guard
covered two records instead of four while still passing. Restore the
sync8 pair under its own comment and keep the sync9b pair after it.
The freshness test passes again (9) and the frontend confirmation-gate
test for the moved sealed report passes (8).
Co-Authored-By: Grok (agent loop)
- Sidebar 「生时校正」 under 星盘, behind a new account `rectification` flag.
- /rectification page: status card from entry-summary (not started / in
progress with window and confirmed event count / adopted time), a short
data check, then the home rectification surface (?rectify=1, or
?rectify=new to start over; ?c=<id> to continue or view). No price on
the page.
- entry-summary gains session id, candidate range, confirmed evidence count
and adopted times (add-only migration 20261028170000).
- The offer card under a consultation answer becomes one line and a link
to /rectification; an explicit request in chat also goes there. No case
is opened from a conversation any more.
- Starter home shows a rectification prompt for self charts with an
estimated time; onboarding checklist gains 做一次生时校正 for them.
- Chart-page link points to /rectification. Shell registers its router so
home hooks can navigate client-side without useRouter in Home().
- Docs: VOICE, DESIGN, CHANGELOG, device checklist.
Self profile only; other people are issue #160.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The freshness test's IDENTITY_FORBIDDEN_PREFIXES list is append-only;
the branch replaced the two sync8 entries instead of adding the sync9b
ones after them, which weakens the identity guard. Restore them.
Co-Authored-By: Claude (agent loop) <noreply@anthropic.com>
Upstream 50847e57, ported semantically:
- Sarpa Drekkana now accepts the five classical ranges (Cancer 2/3,
Scorpio 1/2, Pisces 3) and partitions on the raw longitude instead of
the rounded display degrees.
- The 22nd Drekkana is the eighth house of D3 (+7), not +21 zodiacal
ten-degree divisions.
scripts/varga.py is part of the extended frozen identity, so the sealed
holdout and the reported-offset sweep were re-frozen under ERR-110:
20/20 sealed trials and 900/900 sweep trials match the sync8 records on
every compared field, and the metrics, summary and widening geometry are
unchanged.
Tests: the upstream varga/ sarpa files are added under
tests/test_upstream_sync9b_*.py; the stale +21 assertion and the sarpa
definition string are updated with the original/new/reason columns in
PROGRESS.
Co-Authored-By: Grok (agent loop)
Product chose A on #159: change the frozen varga.py for the five Sarpa
drekkanas and the 22nd drekkana, and refreeze only on identical 20/900
rectification scores. Narayana AD strength (B2) is deferred: our code
has no strength-profile family for it to propagate.
Co-Authored-By: Claude (agent loop) <noreply@anthropic.com>
The single ThreadingHTTPServer ran pure-Python v5 score / block_scan / report
reference under one GIL, so the gate's two slots shared a core. start_server
now starts a spawn pool (workers = heavy concurrency, PYTHONHASHSEED=0, engine
pre-imported); those three routes call the same compute method in a worker.
Over HTTP: score alone 4.74 s, two at once 5.01 / 4.58 s (1.06x; in-process
1.56x). Results equal except per-process fingerprints; report markdown equal.
~165 MB extra. A crashed pool answers inline and restarts; off with
JYOTISH_HEAVY_COMPUTE_PROCESSES=0; tests calling compute methods unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>