Compare commits

..
Author SHA1 Message Date
m 40b7bd1c14 chore: close DEV-000074 checkpoint with completion state
CI / test (push) Successful in 19s
2026-08-18 00:20:08 +03:00
m 1c334fcce6 chore: consolidate DEV-000074 cold-start continuity governance 2026-08-18 00:19:54 +03:00
m fd59b93463 DEV-000072: classify identity naming preference as directive and avoid assertion
CI / test (push) Successful in 19s
2026-08-18 00:04:10 +03:00
21 changed files with 877 additions and 670 deletions
+64 -6
View File
@@ -2,16 +2,45 @@
You are implementing this repository under a frozen architecture generation. **Capability to edit files is not authority to change architecture.**
## Durable project principles
- Domovoy is a long-lived personal assistant, not a one-off chatbot or coding helper.
- Current specialization is temporary; architecture is implementation-specific and revisable by approved architecture decisions.
- Codex is an implementation capability, not architectural owner by default.
- Model/provider is implementation infrastructure, not Domovoy identity.
- Human reality claims are only claims until supported.
- Human intent/preferences are strong evidence, even when not external facts.
- UNKNOWN is a valid state and must remain explicit.
- Reversible/low-risk safety is required before irreversible action.
- Deterministic reflex is optimization, not final semantic truth.
- Capability, transport, and implementation are separate concerns.
- Capability usage requires declared evidence and observed capability gap handling.
- LLM cost/brand/availability is evidence, not a blanket truth rule.
- Experience is not learning by itself; only measured behavior change becomes evidence.
- Normal user interaction must stay zero-ops.
- Live configuration and canonical state should be portable and bootstrap-only environment should stay minimal.
- Production continuity must include recovery and provenance across model/binding/config/loader/operator changes.
- Do not overbuild future systems for current milestone.
## Mandatory startup sequence
Before any non-trivial change:
1. Read `architecture/ARCHITECTURE_CONSTITUTION.md`.
2. Read `architecture/CORE_SEMANTIC_MODEL_V1.md`.
3. Read `architecture/architecture-registry.yaml` and `architecture/architecture-registry.json`.
4. Read `development/CODEX_DEVELOPMENT_CONTRACT.md`.
5. Read the active file under `dev/tasks/`.
6. Run `go run ./cmd/devctl preflight -task <task-file>` for Tier B+ tasks when available.
1. Inspect git branch, status, and recent ancestry (at least `git status`, `git rev-parse`, `git log -1`).
2. Read `AGENTS.md`.
3. Read `architecture/ARCHITECTURE_CONSTITUTION.md`.
4. Read `architecture/CORE_SEMANTIC_MODEL_V1.md`.
5. Read `architecture/architecture-registry.yaml` and `architecture/architecture-registry.json`.
6. Read `development/CODEX_DEVELOPMENT_CONTRACT.md`.
7. Read `dev/ACTIVE_TASK.json`.
8. Read the current task packet under `dev/tasks/` referenced by `ACTIVE_TASK`.
9. Read relevant accepted ADRs from `architecture/adrs`.
10. Inspect `docs/PROJECT_STATUS.md` and current evidence artifacts for checkpoint continuity.
11. Inspect current runtime evidence/check commands before implementation.
12. Run `go run ./cmd/devctl preflight -task <task-file>` for Tier B+ tasks.
13. Then proceed with implementation.
For a fresh session, this sequence is the required cold-start reconstruction path and must be documented in the task checkpoint.
If documents conflict, authority order is:
@@ -72,3 +101,32 @@ State:
- assumptions;
- findings not changed;
- gaps or limitations.
## Session reconstruction checkpoint (Durable memory)
Before leaving a DEV, include machine-readable checkpoint facts in:
- `dev/ACTIVE_TASK.json`
- active task packet under `dev/tasks/`
- `docs/PROJECT_STATUS.md`
- `docs/COLD_START_AND_DURABLE_CHECKPOINT.md`
The checkpoint must explicitly carry:
- `STATUS`
- `CURRENT_PHASE`
- `COMPLETED`
- `IN_PROGRESS`
- `NOT_STARTED`
- `BLOCKERS`
- `IMPORTANT_FINDINGS`
- `EVIDENCE_OBTAINED`
- `LAST_VERIFIED_COMMIT`
- `NEXT_ACTION`
No product behavior or canonical semantic changes are required for this milestone.
The fresh-session recovery procedure and evidence expectations are documented in:
`docs/COLD_START_AND_DURABLE_CHECKPOINT.md`.
Execution-window exhaustion is continuation, not failure; do not mark a task done only because a session ends.
-23
View File
@@ -32,29 +32,6 @@ func TestChatPageServedWithoutInternalIDs(t *testing.T) {
}
}
func TestPublicSurfacesSeparateChatAndConsole(t *testing.T) {
handler, err := newHandler("http://core", "http://continuity", "http://commit", http.DefaultClient, "http://cognitive")
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
path string
must string
mustNot string
}{
{path: "/", must: "Напишите Домовому обычное сообщение", mustNot: "context-preset"},
{path: "/admin", must: "context-preset", mustNot: "Напишите Домовому обычное сообщение"},
{path: "/chat", must: "Напишите Домовому обычное сообщение", mustNot: "context-preset"},
} {
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, test.path, nil))
body := recorder.Body.String()
if recorder.Code != http.StatusOK || !strings.Contains(body, test.must) || strings.Contains(body, test.mustNot) {
t.Fatalf("%s response=%d contains expected=%t forbidden=%t", test.path, recorder.Code, strings.Contains(body, test.must), strings.Contains(body, test.mustNot))
}
}
}
func TestChatMessageUsesCoreAndLocalOnlyCognitiveGateway(t *testing.T) {
var cognitiveRequest cognitive.GatewayAdvisoryRequest
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+1 -17
View File
@@ -112,12 +112,7 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if r.URL.Path == "/" {
serveEmbeddedHTML(w, "chat.html")
return
}
if r.URL.Path == "/admin" {
serveEmbeddedHTML(w, "index.html")
return
w.Header().Set("Cache-Control", "no-store")
}
if r.URL.Path == "/chat" {
r = r.Clone(r.Context())
@@ -127,17 +122,6 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.static.ServeHTTP(w, r)
}
func serveEmbeddedHTML(w http.ResponseWriter, name string) {
body, err := staticFiles.ReadFile("static/" + name)
if err != nil {
http.Error(w, "page unavailable", http.StatusNotFound)
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(body)
}
type browserAdvisoryRequest struct {
Message string `json:"message"`
SessionID string `json:"session_id"`
+1 -12
View File
@@ -229,21 +229,10 @@ func TestConsoleAndSecurityHeaders(t *testing.T) {
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodGet, "/admin", nil)
request := httptest.NewRequest(http.MethodGet, "/", nil)
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "Домовой") ||
!strings.Contains(response.Body.String(), "context-preset") ||
!strings.Contains(response.Body.String(), "context-state") ||
!strings.Contains(response.Body.String(), "copy-context") ||
!strings.Contains(response.Body.String(), "К содержимому") ||
!strings.Contains(response.Body.String(), "aria-current=\"location\"") ||
!strings.Contains(response.Body.String(), "section-nav-link") ||
!strings.Contains(response.Body.String(), "data-page=\"overview\"") ||
!strings.Contains(response.Body.String(), "data-page=\"diagnostics\"") ||
!strings.Contains(response.Body.String(), "data-page=\"operations\"") ||
!strings.Contains(response.Body.String(), "data-page=\"learning\"") ||
!strings.Contains(response.Body.String(), "data-page=\"settings\"") ||
!strings.Contains(response.Body.String(), "satisfaction-dialog") || !strings.Contains(response.Body.String(), "Подтверждаю переход") ||
!strings.Contains(response.Body.String(), "action-history") || !strings.Contains(response.Body.String(), "Журнал действий") ||
!strings.Contains(response.Body.String(), "advisory-form") || !strings.Contains(response.Body.String(), "Спросить Домового") ||
+14 -111
View File
@@ -1,12 +1,12 @@
:root {
--ink: #eef2ec;
--muted: #a7b2aa;
--faint: #718078;
--base: #0f1512;
--panel: #17201b;
--panel-high: #202b25;
--line: #34423a;
--line-soft: #26332c;
--ink: #e9eee7;
--muted: #8c9a91;
--faint: #58645d;
--base: #0b100e;
--panel: #101714;
--panel-high: #151e1a;
--line: #2a352f;
--line-soft: #1d2822;
--signal: #c9a85f;
--signal-bright: #e7c97b;
--good: #78c6a3;
@@ -21,15 +21,13 @@
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { background: var(--base); color: var(--ink); font-family: var(--sans); scroll-behavior: smooth; }
body { margin: 0; min-width: 320px; min-height: 100vh; background: radial-gradient(circle at 73% -10%, rgba(83, 123, 101, .13), transparent 38%), var(--base); }
html { background: var(--base); color: var(--ink); font-family: var(--sans); }
body { margin: 0; min-width: 320px; min-height: 100vh; background: radial-gradient(circle at 73% -10%, rgba(83, 123, 101, .15), transparent 38%), var(--base); }
button, input, textarea { font: inherit; }
button { color: inherit; }
button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible { outline: 2px solid var(--signal-bright); outline-offset: 3px; }
.skip-link { position: fixed; top: 10px; left: 10px; z-index: 30; padding: 10px 13px; color: #11150f; background: var(--signal-bright); font: 600 10px/1 var(--mono); text-decoration: none; transform: translateY(-160%); transition: transform .18s; }
.skip-link:focus { transform: translateY(0); }
button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--signal-bright); outline-offset: 3px; }
.grain { position: fixed; inset: 0; pointer-events: none; opacity: .045; z-index: 10; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); }
.grain { position: fixed; inset: 0; pointer-events: none; opacity: .18; z-index: 10; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); }
.eyebrow, .rail-index { margin: 0 0 12px; color: var(--signal); font: 600 11px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; }
.login-shell { min-height: 100vh; display: grid; place-items: center; padding: 32px 20px; position: relative; overflow: hidden; }
@@ -65,9 +63,6 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
.wordmark strong { font: 400 20px/1 var(--serif); }
.wordmark small { margin-top: 4px; color: var(--muted); font: 9px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; }
.topbar-actions { display: flex; align-items: center; gap: 10px; }
.topbar-nav { display: flex; align-items: center; gap: 4px; margin-right: 8px; }
.topbar-nav a { padding: 9px 10px; color: var(--muted); text-decoration: none; font: 600 9px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.topbar-nav a:hover, .topbar-nav a:focus-visible { color: var(--ink); background: var(--panel-high); outline: 0; }
.connection { margin-right: 12px; color: var(--muted); font: 10px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.connection i { width: 7px; height: 7px; display: inline-block; margin-right: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 14px rgba(120, 198, 163, .6); }
.connection.offline i { background: var(--bad); box-shadow: none; }
@@ -78,55 +73,17 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
.context-rail h2 { margin: 0; font: 400 29px/1.1 var(--serif); }
.rail-copy { margin: 14px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
.context-form { display: grid; }
.context-status-row { margin-bottom: 16px; padding: 10px 11px; display: flex; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--line-soft); background: rgba(120,198,163,.04); }
.context-status-label { color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .1em; }
.context-status-row strong { color: var(--warn); font: 600 9px/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; }
.context-status-row strong[data-state="ready"] { color: var(--good); }
.context-presets { display: grid; gap: 7px; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid var(--line-soft); }
.context-presets label { margin: 0; }
.context-presets select { width: 100%; min-height: 38px; padding: 0 9px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); background: #0b110e; font: 10px/1 var(--mono); }
.context-presets small { color: var(--faint); font-size: 10px; line-height: 1.35; }
.context-field { display: grid; gap: 7px; }
.context-field + .context-field { margin-top: 13px; }
.context-field label { margin: 0; }
.context-field label span { color: var(--signal); font-size: 8px; letter-spacing: .06em; }
.context-input-row { display: grid; grid-template-columns: minmax(0, 1fr) 34px; gap: 5px; }
.context-input-row input { min-width: 0; }
.copy-context { width: 34px; height: 40px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--muted); background: transparent; cursor: pointer; }
.copy-context:hover { color: var(--ink); border-color: var(--faint); background: var(--panel-high); }
.id-actions { min-height: 31px; display: flex; align-items: center; gap: 7px; margin-top: 12px; }
.id-action { min-height: 30px; padding: 0 9px; border: 1px solid var(--line-soft); border-radius: var(--radius); color: var(--muted); background: transparent; cursor: pointer; font: 9px/1 var(--mono); letter-spacing: .04em; }
.id-action:hover { color: var(--ink); border-color: var(--signal); background: rgba(201,168,95,.07); }
.icon-id-action { width: 30px; padding: 0; color: var(--faint); }
.context-form label { margin-top: 15px; }
.context-form label:first-child { margin-top: 0; }
.context-form input { height: 40px; padding: 0 10px; font: 11px/1 var(--mono); }
.context-hint { margin: 12px 0 0; color: var(--faint); font-size: 10px; line-height: 1.45; }
.advanced-context { margin-top: 17px; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
.advanced-context summary { padding: 12px 0; color: var(--muted); cursor: pointer; list-style: none; font: 600 9px/1.2 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.advanced-context summary::before { content: "+"; display: inline-block; width: 18px; color: var(--signal); font-size: 13px; }
.advanced-context[open] summary::before { content: "−"; }
.advanced-context > div { padding: 0 0 10px; }
.advanced-context label { margin-top: 13px; }
.context-actions { display: grid; gap: 8px; }
.context-actions .secondary-button { margin-top: 18px; }
.context-actions .text-button { margin: 0; justify-self: start; padding-inline: 0; }
.secondary-button { height: 42px; margin-top: 20px; color: #11150f; background: var(--signal); font-size: 12px; }
.section-nav { display: grid; gap: 3px; margin-top: 4px; }
.section-nav-link { display: grid; grid-template-columns: 28px 1fr; gap: 2px 10px; padding: 11px 10px; border-left: 2px solid transparent; color: var(--muted); text-decoration: none; transition: background .18s, border-color .18s, color .18s; }
.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active, .section-nav-link[aria-current="location"] { border-left-color: var(--signal); color: var(--ink); background: rgba(201,168,95,.07); outline: 0; }
.section-nav-link span { grid-row: 1 / span 2; color: var(--signal); font: 9px/1.4 var(--mono); }
.section-nav-link b { font: 600 11px/1.2 var(--sans); }
.section-nav-link small { color: var(--faint); font: 9px/1.2 var(--mono); }
.session-stamp { margin-top: auto; padding-top: 20px; display: grid; gap: 6px; border-top: 1px solid var(--line); }
.session-stamp span { margin-top: 8px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .14em; }
.session-stamp strong { overflow: hidden; color: var(--muted); font: 10px/1.4 var(--mono); text-overflow: ellipsis; }
.workspace { grid-column: 2; grid-row: 2; width: min(100%, 1740px); justify-self: start; padding: 42px clamp(24px, 3vw, 48px) 64px; min-width: 0; }
.workspace > [data-page][hidden] { display: none !important; }
.workspace > section { scroll-margin-top: 94px; }
.workspace { grid-column: 2; grid-row: 2; padding: clamp(28px, 4vw, 58px); min-width: 0; }
.workspace-head { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-bottom: 34px; }
.workspace-head h1 { margin: 0; font: 400 clamp(34px, 4vw, 50px)/1 var(--serif); letter-spacing: -.035em; }
.workspace-head h1 { margin: 0; font: 400 clamp(36px, 5vw, 58px)/1 var(--serif); letter-spacing: -.035em; }
.last-update { color: var(--faint); font: 10px/1.4 var(--mono); text-align: right; }
.signal-strip { display: grid; grid-template-columns: repeat(4, 1fr); margin-bottom: 18px; border: 1px solid var(--line); background: var(--panel); }
.signal-strip div { padding: 16px 18px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-right: 1px solid var(--line); }
@@ -137,46 +94,6 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
.signal-strip strong[data-state="error"] { color: var(--bad); }
.signal-strip strong[data-state="loading"] { color: var(--warn); animation: pulse 1.1s ease-in-out infinite; }
.diagnostics-panel, .settings-panel { margin-bottom: 18px; border: 1px solid var(--line); background: rgba(16,23,20,.82); }
.diagnostics-panel { border-color: rgba(120,198,163,.32); box-shadow: inset 3px 0 0 rgba(120,198,163,.45); }
.panel-heading { min-height: 68px; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid var(--line); }
.panel-heading p { margin: 0 0 6px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .13em; text-transform: uppercase; }
.panel-heading h2 { margin: 0; font: 400 23px/1 var(--serif); }
.panel-heading-tools { display: flex; align-items: center; gap: 14px; }
.panel-heading-tools > span, .settings-saved { color: var(--muted); font: 9px/1.3 var(--mono); letter-spacing: .06em; text-transform: uppercase; }
.compact-button { width: auto; min-width: 142px; height: 38px; margin: 0; padding: 0 13px; }
.diagnostic-intro { padding: 13px 18px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--line-soft); color: var(--muted); }
.diagnostic-intro p { margin: 0; font-size: 11px; line-height: 1.45; }
.diagnostic-pulse { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; background: var(--good); box-shadow: 0 0 12px rgba(120,198,163,.7); }
.diagnostic-list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.diagnostic-list[aria-busy="true"] { opacity: .7; }
.diagnostic-item { min-width: 0; min-height: 92px; padding: 14px 16px; display: grid; grid-template-columns: 9px 1fr auto; gap: 9px; align-items: start; border-right: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
.diagnostic-item:nth-child(4n) { border-right: 0; }
.diagnostic-item > i { width: 7px; height: 7px; margin-top: 4px; border-radius: 50%; background: var(--faint); }
.diagnostic-item[data-state="ok"] > i { background: var(--good); box-shadow: 0 0 9px rgba(120,198,163,.6); }
.diagnostic-item[data-state="error"] > i { background: var(--bad); }
.diagnostic-item[data-state="loading"] > i { background: var(--warn); animation: pulse 1.1s ease-in-out infinite; }
.diagnostic-item strong { display: block; color: var(--ink); font: 600 10px/1.3 var(--mono); }
.diagnostic-item span { display: block; margin-top: 6px; color: var(--muted); font-size: 10px; line-height: 1.35; }
.diagnostic-item em { color: var(--faint); font: normal 9px/1.2 var(--mono); text-transform: uppercase; }
.diagnostic-item[data-state="ok"] em { color: var(--good); }
.diagnostic-item[data-state="error"] em { color: var(--bad); }
.settings-grid { padding: 18px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; }
.setting-field, .setting-check { min-width: 0; display: grid; gap: 7px; color: var(--ink); }
.setting-field > span, .setting-check b { font: 600 10px/1.2 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.setting-field small, .setting-check small { color: var(--muted); font-size: 11px; line-height: 1.4; }
.setting-field select { width: 100%; min-height: 40px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); background: #0b110e; font: 11px/1 var(--mono); }
.setting-check { grid-template-columns: 18px 1fr; align-items: start; gap: 10px; padding: 10px 12px; border: 1px solid var(--line-soft); }
.setting-check input { width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--signal); }
.setting-check span { display: grid; gap: 7px; }
.settings-actions { display: flex; align-items: center; justify-content: flex-end; gap: 14px; grid-column: 1 / -1; padding-top: 2px; }
.settings-actions .text-button { margin: 0; }
html[data-density="compact"] .data-card { padding: 18px; }
html[data-density="compact"] .data-card dl div { padding-block: 10px; }
html[data-density="compact"] .activity-row { min-height: 58px; padding-block: 8px; }
html[data-density="compact"] .learning-row { min-height: 54px; padding-block: 9px; }
html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before, html[data-reduced-motion="true"] *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; }
.advisor-panel { margin-bottom: 18px; padding: 24px; display: grid; grid-template-columns: minmax(220px, .72fr) minmax(320px, 1.28fr); gap: 18px 34px; border: 1px solid rgba(201,168,95,.42); background: linear-gradient(125deg, rgba(201,168,95,.1), rgba(16,23,20,.88) 44%, rgba(12,20,17,.96)); box-shadow: inset 3px 0 0 rgba(201,168,95,.58); }
.advisor-intro { align-self: center; }
.advisor-intro > p { margin: 0 0 10px; color: var(--signal); font: 600 9px/1 var(--mono); letter-spacing: .15em; }
@@ -361,9 +278,6 @@ html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before,
.advisor-panel { grid-template-columns: 1fr; }
.capability-card { grid-column: auto; }
.prospective-card { grid-column: auto; }
.diagnostic-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.diagnostic-item:nth-child(4n) { border-right: 1px solid var(--line-soft); }
.diagnostic-item:nth-child(2n) { border-right: 0; }
}
@media (max-width: 680px) {
@@ -371,25 +285,14 @@ html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before,
.console { display: block; }
.topbar { height: 66px; padding: 0 14px; }
.wordmark small, .connection, .text-button { display: none; }
.topbar-nav { display: none; }
.context-rail { padding: 24px 18px; border-right: 0; border-bottom: 1px solid var(--line); }
.rail-copy { max-width: 48ch; }
.session-stamp { margin-top: 8px; grid-template-columns: auto 1fr; align-items: center; }
.session-stamp span { margin: 0; }
.workspace { padding: 30px 16px 50px; }
.section-nav { display: flex; gap: 6px; margin: 10px -18px -24px; padding: 10px 18px; overflow-x: auto; border-top: 1px solid var(--line-soft); background: #0e1512; scrollbar-width: thin; }
.section-nav-link { min-width: 142px; padding: 10px; border-left: 0; border-bottom: 2px solid transparent; }
.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active, .section-nav-link[aria-current="location"] { border-left-color: transparent; border-bottom-color: var(--signal); }
.workspace-head { align-items: start; flex-direction: column; }
.last-update { text-align: left; }
.signal-strip { grid-template-columns: 1fr; }
.panel-heading { align-items: flex-start; flex-direction: column; }
.panel-heading-tools { width: 100%; justify-content: space-between; }
.diagnostic-list { grid-template-columns: 1fr; }
.diagnostic-item, .diagnostic-item:nth-child(2n), .diagnostic-item:nth-child(4n) { border-right: 0; }
.settings-grid { grid-template-columns: 1fr; padding: 16px; }
.settings-actions { grid-column: auto; align-items: stretch; flex-direction: column; }
.settings-actions .primary-button { width: 100%; }
.signal-strip div { border-right: 0; border-bottom: 1px solid var(--line); }
.advisor-panel { padding: 20px 16px; gap: 20px; }
.advisor-controls { align-items: stretch; flex-direction: column; }
+16 -270
View File
@@ -7,42 +7,14 @@ const advisoryForm = byId("advisory-form");
const loginError = byId("login-error");
const toast = byId("toast");
const contextFields = ["session-id", "workspace-id", "task-id", "prospective-id", "capability-id"];
const settingsStorageKey = "domovoy.creator.settings";
const contextPresetsStorageKey = "domovoy.creator.context.presets";
let sessionToken = sessionStorage.getItem("domovoy.creator.session") || "";
let toastTimer;
let refreshTimer;
let currentTask = null;
let currentProspective = null;
let pendingCommand = null;
let pendingNotification = null;
let lastAuthorizedActionID = "";
let advisoryPolicy = {Mode: "deterministic", ExternalEgress: false, AcknowledgementRequired: false};
let uiSettings = {density: "comfortable", refresh: "off", motion: false};
const pageNavLinks = Array.from(document.querySelectorAll(".section-nav-link, .topbar-nav a"));
const pageViews = Array.from(document.querySelectorAll("[data-page]"));
const cabinetPages = ["overview", "diagnostics", "operations", "learning", "settings"];
function setActivePage(id, updateHistory = false) {
const page = cabinetPages.includes(id) ? id : "overview";
pageViews.forEach((view) => { view.hidden = view.dataset.page !== page; });
document.body.dataset.page = page;
pageNavLinks.forEach((link) => {
const active = link.getAttribute("href") === `#${page}`;
link.classList.toggle("active", active);
if (active) link.setAttribute("aria-current", "location");
else link.removeAttribute("aria-current");
});
if (updateHistory && window.location.hash !== `#${page}`) window.history.pushState({}, "", `#${page}`);
}
pageNavLinks.forEach((link) => link.addEventListener("click", (event) => {
event.preventDefault();
setActivePage(link.getAttribute("href").slice(1), true);
}));
window.addEventListener("popstate", () => setActivePage(window.location.hash.slice(1)));
setActivePage(window.location.hash.slice(1));
function setText(id, value, fallback = "—") {
byId(id).textContent = value === undefined || value === null || value === "" ? fallback : String(value);
@@ -67,18 +39,6 @@ function notify(message) {
toastTimer = setTimeout(() => { toast.hidden = true; }, 4200);
}
function safeClientError(error) {
const message = error && typeof error.message === "string" ? error.message : "";
switch (message) {
case "creator authentication denied":
return "Логин или пароль не принят.";
case "Сессия отклонена":
return "Сессия истекла. Войдите снова.";
default:
return "Операция недоступна. Попробуйте ещё раз.";
}
}
async function request(path, options = {}) {
const headers = new Headers(options.headers || {});
if (sessionToken) headers.set("Authorization", `Bearer ${sessionToken}`);
@@ -90,9 +50,7 @@ async function request(path, options = {}) {
}
if (!response.ok) {
const detail = (await response.text()).trim();
const error = new Error(detail || `Ошибка ${response.status}`);
error.status = response.status;
throw error;
throw new Error(detail || `Ошибка ${response.status}`);
}
return response.json();
}
@@ -106,57 +64,15 @@ function restoreContext() {
} catch (_) {
localStorage.removeItem("domovoy.creator.context");
}
renderContextState();
renderContextPresets();
}
function saveContext() {
const saved = {};
contextFields.forEach((id) => { saved[id] = byId(id).value.trim(); });
localStorage.setItem("domovoy.creator.context", JSON.stringify(saved));
renderContextState();
return saved;
}
function readContextPresets() {
try {
const value = JSON.parse(localStorage.getItem(contextPresetsStorageKey) || "[]");
return Array.isArray(value) ? value.filter((item) => item && item.session && item.workspace) : [];
} catch (_) {
localStorage.removeItem(contextPresetsStorageKey);
return [];
}
}
function renderContextState() {
const state = byId("context-state");
if (!state) return;
const ready = Boolean(byId("session-id").value.trim() && byId("workspace-id").value.trim());
state.textContent = ready ? "готов к проверке" : "не выбран";
state.dataset.state = ready ? "ready" : "empty";
}
function renderContextPresets() {
const select = byId("context-preset");
if (!select) return;
const presets = readContextPresets();
select.replaceChildren(new Option("Выберите сохранённый набор…", ""));
presets.forEach((preset, index) => {
const session = preset.session.length > 18 ? `${preset.session.slice(0, 8)}…${preset.session.slice(-6)}` : preset.session;
const workspace = preset.workspace.length > 18 ? `${preset.workspace.slice(0, 8)}…${preset.workspace.slice(-6)}` : preset.workspace;
select.appendChild(new Option(`${session} · ${workspace}`, String(index)));
});
}
function rememberContextPreset(context) {
if (!context["session-id"] || !context["workspace-id"]) return;
const next = {session: context["session-id"], workspace: context["workspace-id"], savedAt: Date.now()};
const presets = readContextPresets().filter((item) => item.session !== next.session || item.workspace !== next.workspace);
presets.unshift(next);
localStorage.setItem(contextPresetsStorageKey, JSON.stringify(presets.slice(0, 5)));
renderContextPresets();
}
async function showConsole() {
const session = await request("/api/session/current");
setText("session-actor", session.ActorID);
@@ -164,7 +80,6 @@ async function showConsole() {
setText("session-expiry", expiry);
loginView.hidden = true;
consoleView.hidden = false;
restoreSettings();
restoreContext();
await loadAdvisoryPolicy();
const context = saveContext();
@@ -173,13 +88,10 @@ async function showConsole() {
loadActionHistory();
loadLearning();
}
runDiagnostics();
}
function endSession(message = "Сессия завершена.") {
sessionToken = "";
clearInterval(refreshTimer);
refreshTimer = null;
sessionStorage.removeItem("domovoy.creator.session");
consoleView.hidden = true;
loginView.hidden = false;
@@ -216,7 +128,7 @@ loginForm.addEventListener("submit", async (event) => {
byId("creator-password").value = "";
await showConsole();
} catch (error) {
loginError.textContent = safeClientError(error);
loginError.textContent = error.message === "creator authentication denied" ? "Логин или пароль не принят." : error.message;
} finally {
button.disabled = false;
button.firstElementChild.textContent = "Открыть консоль";
@@ -232,129 +144,14 @@ byId("toggle-secret").addEventListener("click", () => {
contextForm.addEventListener("submit", (event) => {
event.preventDefault();
const context = saveContext();
rememberContextPreset(context);
saveContext();
loadDashboard();
});
for (const input of [byId("session-id"), byId("workspace-id")]) {
input.addEventListener("input", renderContextState);
}
byId("context-preset").addEventListener("change", (event) => {
const index = Number(event.target.value);
if (!Number.isInteger(index)) return;
const preset = readContextPresets()[index];
if (!preset) return;
byId("session-id").value = preset.session;
byId("workspace-id").value = preset.workspace;
renderContextState();
loadDashboard();
notify("Контекст из недавнего набора загружен.");
});
for (const button of document.querySelectorAll("[data-copy-context]")) {
button.addEventListener("click", async () => {
const value = byId(button.dataset.copyContext).value.trim();
if (!value) {
notify("Сначала укажите идентификатор.");
return;
}
try {
await navigator.clipboard.writeText(value);
notify("Идентификатор скопирован.");
} catch (_) {
notify("Не удалось скопировать идентификатор.");
}
});
}
for (const button of document.querySelectorAll("[data-copy-value], [data-use-value]")) {
button.addEventListener("click", async () => {
const source = byId(button.dataset.copyValue || button.dataset.useValue);
const value = source?.textContent.trim() || "";
if (!value || value === "—") {
notify("Сначала загрузите значение.");
return;
}
if (button.dataset.useValue) {
const target = byId(button.dataset.targetId);
if (!target) return;
target.value = value;
document.querySelector(".advanced-context").open = true;
saveContext();
notify("ID подставлен в расширенную проверку.");
return;
}
try {
await navigator.clipboard.writeText(value);
notify("ID скопирован.");
} catch (_) {
notify("Не удалось скопировать ID.");
}
});
}
byId("reset-context").addEventListener("click", () => {
contextFields.forEach((id) => { byId(id).value = id === "capability-id" ? "cognitive-proposal" : ""; });
localStorage.removeItem("domovoy.creator.context");
loadDashboard();
notify("Рабочий контекст сброшен.");
});
function restoreSettings() {
try {
const saved = JSON.parse(localStorage.getItem(settingsStorageKey) || "{}");
uiSettings = {
density: ["comfortable", "compact"].includes(saved.density) ? saved.density : "comfortable",
refresh: ["off", "30", "60", "300"].includes(saved.refresh) ? saved.refresh : "off",
motion: saved.motion === true,
};
} catch (_) {
uiSettings = {density: "comfortable", refresh: "off", motion: false};
}
byId("setting-density").value = uiSettings.density;
byId("setting-refresh").value = uiSettings.refresh;
byId("setting-motion").checked = uiSettings.motion;
applySettings();
}
function applySettings() {
document.documentElement.dataset.density = uiSettings.density;
document.documentElement.dataset.reducedMotion = uiSettings.motion ? "true" : "false";
clearInterval(refreshTimer);
refreshTimer = null;
if (sessionToken && uiSettings.refresh !== "off") {
refreshTimer = setInterval(() => loadDashboard(), Number(uiSettings.refresh) * 1000);
}
}
byId("settings-form").addEventListener("submit", (event) => {
event.preventDefault();
uiSettings = {
density: byId("setting-density").value,
refresh: byId("setting-refresh").value,
motion: byId("setting-motion").checked,
};
localStorage.setItem(settingsStorageKey, JSON.stringify(uiSettings));
applySettings();
setText("settings-saved", "Сохранено только в этом браузере");
notify("Настройки кабинета сохранены.");
});
byId("reset-settings").addEventListener("click", () => {
uiSettings = {density: "comfortable", refresh: "off", motion: false};
localStorage.removeItem(settingsStorageKey);
restoreSettings();
setText("settings-saved", "Восстановлены значения по умолчанию");
notify("Настройки сброшены.");
});
byId("refresh-all").addEventListener("click", loadDashboard);
byId("refresh-actions").addEventListener("click", loadActionHistory);
byId("refresh-learning").addEventListener("click", loadLearning);
byId("logout").addEventListener("click", () => endSession());
byId("run-diagnostics").addEventListener("click", runDiagnostics);
advisoryForm.addEventListener("submit", async (event) => {
event.preventDefault();
@@ -388,7 +185,7 @@ advisoryForm.addEventListener("submit", async (event) => {
byId("advisory-result").hidden = false;
} catch (error) {
setText("advisory-state", "ОШИБКА");
setText("advisory-text", safeClientError(error));
setText("advisory-text", error.message);
byId("advisory-result").hidden = false;
} finally {
byId("advisory-egress-ack").checked = false;
@@ -449,57 +246,6 @@ async function loadDashboard() {
setText("last-update", `Обновлено ${new Intl.DateTimeFormat("ru-RU", {hour: "2-digit", minute: "2-digit", second: "2-digit"}).format(new Date())}`);
}
async function runDiagnostics() {
const list = byId("diagnostic-list");
const summary = byId("diagnostic-summary");
const checks = [
["Сессия", "identity", () => request("/api/session/current")],
["Политика совета", "advisory", () => request("/api/advisory-policy")],
["Когнитивная возможность", "capability", () => request("/api/capabilities/cognitive-proposal/health")],
["Сегодня", "today", () => request("/api/today")],
["Внимание", "attention", () => request("/api/attention")],
["Действия", "actions", () => request("/api/actions?limit=1")],
["Learning evidence", "learning", () => request("/api/learning/recent?limit=1")],
];
list.setAttribute("aria-busy", "true");
list.replaceChildren();
checks.forEach(([label, key]) => list.append(createDiagnosticItem(label, key, "loading")));
setText("diagnostic-summary", "Проверяю…");
const results = await Promise.all(checks.map(async ([label, key, run]) => {
const started = performance.now();
try {
await run();
return {label, key, state: "ok", detail: `Ответ получен · ${Math.round(performance.now() - started)} мс`};
} catch (error) {
const detail = error?.status === 400 ? "Нужны параметры рабочего контекста" : error?.status === 401 ? "Сессия истекла" : error?.status >= 500 ? "Сервис временно недоступен" : "Проверка не пройдена";
return {label, key, state: "error", detail};
}
}));
list.replaceChildren(...results.map((result) => createDiagnosticItem(result.label, result.key, result.state, result.detail)));
list.setAttribute("aria-busy", "false");
const passed = results.filter((result) => result.state === "ok").length;
setText("diagnostic-summary", `${passed}/${results.length} проверок пройдено`);
}
function createDiagnosticItem(label, key, state, detail = "Проверяю…") {
const item = document.createElement("article");
item.className = "diagnostic-item";
item.dataset.state = state;
item.dataset.check = key;
const marker = document.createElement("i");
marker.setAttribute("aria-hidden", "true");
const copy = document.createElement("div");
const title = document.createElement("strong");
title.textContent = label;
const description = document.createElement("span");
description.textContent = detail;
copy.append(title, description);
const status = document.createElement("em");
status.textContent = state === "ok" ? "OK" : state === "error" ? "ошибка" : "…";
item.append(marker, copy, status);
return item;
}
async function loadLearning() {
const experienceList = byId("experience-list");
const reflectionList = byId("reflection-list");
@@ -526,7 +272,7 @@ async function loadLearning() {
list.replaceChildren();
const failed = document.createElement("p");
failed.className = "register-empty activity-error";
failed.textContent = safeClientError(error);
failed.textContent = error.message;
list.append(failed);
});
setText("learning-count", "ОШИБКА");
@@ -625,7 +371,7 @@ async function loadActionHistory() {
list.replaceChildren();
const failed = document.createElement("p");
failed.className = "register-empty activity-error";
failed.textContent = safeClientError(error);
failed.textContent = error.message;
list.append(failed);
setText("action-count", "ОШИБКА");
} finally {
@@ -684,7 +430,7 @@ async function reconcileHistoryAction(actionID, button) {
await loadActionHistory();
if (currentProspective) await loadProspectiveRegister(currentProspective.ID);
} catch (error) {
notify(safeClientError(error));
notify(error.message);
button.disabled = false;
button.textContent = "Сверить";
}
@@ -704,7 +450,7 @@ async function loadFocus(query) {
} catch (error) {
setSignal("focus", "ошибка", "error");
setBadge("focus", "НЕДОСТУПЕН", "bad");
setText("focus-message", safeClientError(error));
setText("focus-message", error.message);
byId("connection-state").classList.add("offline");
}
}
@@ -726,7 +472,7 @@ async function loadTask(taskID, query) {
currentTask = null;
setSignal("task", "отклонена", "error");
setBadge("task", "ВНЕ ФОКУСА", "bad");
setText("task-message", safeClientError(error));
setText("task-message", error.message);
updateActionReadiness();
}
}
@@ -765,12 +511,12 @@ async function loadProspectiveRegister(selectedID) {
list.replaceChildren();
const failed = document.createElement("p");
failed.className = "register-empty";
failed.textContent = safeClientError(error);
failed.textContent = error.message;
list.append(failed);
setText("prospective-count", "ОШИБКА");
setSignal("prospective", "ошибка", "error");
setBadge("prospective", "НЕДОСТУПНА", "bad");
setText("prospective-message", safeClientError(error));
setText("prospective-message", error.message);
}
}
@@ -826,7 +572,7 @@ async function loadProspective(prospectiveID) {
setSignal("prospective", "ошибка", "error");
setBadge("prospective", "НЕДОСТУПНА", "bad");
setText("prospective-state", "UNKNOWN");
setText("prospective-message", safeClientError(error));
setText("prospective-message", error.message);
updateActionReadiness();
}
}
@@ -848,7 +594,7 @@ async function loadCapability(capabilityID) {
setSignal("capability", "ошибка", "error");
setBadge("capability", "НЕДОСТУПНА", "bad");
setText("capability-state", "UNKNOWN");
setText("capability-reason", safeClientError(error));
setText("capability-reason", error.message);
}
}
@@ -954,7 +700,7 @@ byId("confirm-notification").addEventListener("click", async () => {
await loadActionHistory();
notify(result.State === "SUCCEEDED" ? "Receiver принял уведомление." : "Исход отправки требует сверки.");
} catch (error) {
setText("notification-confirmation-error", safeClientError(error));
setText("notification-confirmation-error", error.message);
} finally {
button.disabled = false;
button.textContent = "Подтверждаю отправку";
@@ -984,7 +730,7 @@ byId("confirm-satisfaction").addEventListener("click", async () => {
notify(result.State === "SUCCEEDED" ? "Исполнение зафиксировано через Commit." : "Исход требует проверки.");
await loadProspectiveRegister(command.ProspectiveItemID);
} catch (error) {
setText("confirmation-error", safeClientError(error));
setText("confirmation-error", error.message);
} finally {
button.disabled = false;
button.textContent = "Подтверждаю переход";
@@ -1018,7 +764,7 @@ byId("reconcile-action").addEventListener("click", async () => {
notify(result.State === "SUCCEEDED" ? "Исход подтверждён." : "Исход пока неизвестен.");
if (currentProspective) await loadProspectiveRegister(currentProspective.ID);
} catch (error) {
notify(safeClientError(error));
notify(error.message);
} finally {
button.disabled = false;
button.textContent = "Проверить исход";
+2 -2
View File
@@ -25,12 +25,12 @@
<section id="chat-view" class="chat-view" hidden>
<header class="chat-header">
<a href="/" aria-label="Домовой">Домовой</a>
<a href="/chat" aria-label="Домовой">Домовой</a>
<nav class="human-nav" aria-label="Разделы Домового">
<button class="nav-tab active" data-tab="chat-panel" type="button">Чат</button>
<button class="nav-tab" data-tab="today-panel" type="button">Сегодня</button>
<button class="nav-tab" data-tab="history-panel" type="button">История</button>
<a href="/admin" class="operator-link">Консоль</a>
<a href="/" class="operator-link">Оператор</a>
</nav>
<span id="active-focus">фокус: не выбран</span>
<span id="session-state">сессия активна</span>
+20 -86
View File
@@ -6,10 +6,9 @@
<meta name="color-scheme" content="dark">
<meta name="theme-color" content="#101714">
<title>Домовой — консоль создателя</title>
<link rel="stylesheet" href="/assets/console.css?v=20260821-context">
<link rel="stylesheet" href="/assets/console.css">
</head>
<body>
<a class="skip-link" href="#overview">К содержимому</a>
<div class="grain" aria-hidden="true"></div>
<main id="login-view" class="login-shell">
@@ -45,13 +44,6 @@
</a>
<div class="topbar-actions">
<span id="connection-state" class="connection"><i></i>система доступна</span>
<nav class="topbar-nav" aria-label="Разделы кабинета">
<a href="#overview">Обзор</a>
<a href="#diagnostics">Проверки</a>
<a href="#operations">Операции</a>
<a href="#learning">Опыт</a>
<a href="#settings">Настройки</a>
</nav>
<button id="refresh-all" class="text-button" type="button">Обновить</button>
<button id="logout" class="icon-button" type="button" aria-label="Завершить сессию" title="Выйти">↗</button>
</div>
@@ -64,46 +56,18 @@
<p class="rail-copy">Укажите рабочий контекст. Он сохраняется только в этом браузере и не даёт дополнительных полномочий.</p>
</div>
<form id="context-form" class="context-form">
<div class="context-status-row">
<span class="context-status-label">ТЕКУЩИЙ КОНТЕКСТ</span>
<strong id="context-state" data-state="empty">не выбран</strong>
</div>
<div class="context-presets">
<label for="context-preset">Недавние наборы</label>
<select id="context-preset">
<option value="">Выберите сохранённый набор…</option>
</select>
<small>Сохраняются только в этом браузере.</small>
</div>
<div class="context-field">
<label for="session-id">Идентификатор сессии <span>обязательно</span></label>
<div class="context-input-row"><input id="session-id" autocomplete="off" spellcheck="false" placeholder="Вставьте session-id"><button class="copy-context" data-copy-context="session-id" type="button" aria-label="Скопировать идентификатор сессии" title="Скопировать">⧉</button></div>
</div>
<div class="context-field">
<label for="workspace-id">Рабочая область <span>обязательно</span></label>
<div class="context-input-row"><input id="workspace-id" autocomplete="off" spellcheck="false" placeholder="Вставьте workspace-id"><button class="copy-context" data-copy-context="workspace-id" type="button" aria-label="Скопировать рабочую область" title="Скопировать">⧉</button></div>
</div>
<p class="context-hint">Для «Состояния» нужны только эти два значения. Задача, ожидание и возможность находятся ниже в расширенных полях.</p>
<details class="advanced-context">
<summary>Расширенные идентификаторы</summary>
<div>
<label for="task-id">Задача</label>
<input id="task-id" autocomplete="off" placeholder="task-id">
<label for="prospective-id">Выбранное ожидание</label>
<input id="prospective-id" autocomplete="off" placeholder="prospective-item-id">
<label for="capability-id">Возможность</label>
<input id="capability-id" autocomplete="off" value="cognitive-proposal">
</div>
</details>
<div class="context-actions"><button class="secondary-button" type="submit">Загрузить контекст</button><button id="reset-context" class="text-button" type="button">Сбросить</button></div>
<label for="session-id">Идентификатор сессии</label>
<input id="session-id" autocomplete="off" placeholder="session-id">
<label for="workspace-id">Рабочая область</label>
<input id="workspace-id" autocomplete="off" placeholder="workspace-id">
<label for="task-id">Задача</label>
<input id="task-id" autocomplete="off" placeholder="task-id">
<label for="prospective-id">Выбранное ожидание</label>
<input id="prospective-id" autocomplete="off" placeholder="prospective-item-id">
<label for="capability-id">Возможность</label>
<input id="capability-id" autocomplete="off" value="cognitive-proposal">
<button class="secondary-button" type="submit">Загрузить контекст</button>
</form>
<nav class="section-nav" aria-label="Навигация по кабинету">
<a class="section-nav-link active" aria-current="location" href="#overview"><span>01</span><b>Состояние</b><small>фокус и сигналы</small></a>
<a class="section-nav-link" href="#diagnostics"><span>02</span><b>Проверки</b><small>доступность и границы</small></a>
<a class="section-nav-link" href="#operations"><span>03</span><b>Операции</b><small>ожидания и действия</small></a>
<a class="section-nav-link" href="#learning"><span>04</span><b>Опыт</b><small>reflection и навыки</small></a>
<a class="section-nav-link" href="#settings"><span>05</span><b>Настройки</b><small>интерфейс и обновление</small></a>
</nav>
<div class="session-stamp">
<span>ACTOR</span>
<strong id="session-actor">—</strong>
@@ -113,7 +77,7 @@
</aside>
<main class="workspace">
<section id="overview" class="workspace-head" data-page="overview">
<section class="workspace-head">
<div>
<p class="eyebrow">операционная картина</p>
<h1>Состояние сейчас</h1>
@@ -121,28 +85,14 @@
<p id="last-update" class="last-update">Данные ещё не запрошены</p>
</section>
<section class="signal-strip" aria-label="Сводка состояния" data-page="overview">
<section class="signal-strip" aria-label="Сводка состояния">
<div><span>ФОКУС</span><strong id="focus-signal" data-state="idle">ожидает</strong></div>
<div><span>ЗАДАЧА</span><strong id="task-signal" data-state="idle">ожидает</strong></div>
<div><span>CONTINUITY</span><strong id="prospective-signal" data-state="idle">ожидает</strong></div>
<div><span>ВОЗМОЖНОСТЬ</span><strong id="capability-signal" data-state="idle">ожидает</strong></div>
</section>
<section id="diagnostics" class="diagnostics-panel" aria-labelledby="diagnostics-title" data-page="diagnostics">
<header class="panel-heading">
<div><p>Проверяемая система</p><h2 id="diagnostics-title">Диагностика кабинета</h2></div>
<div class="panel-heading-tools"><span id="diagnostic-summary">Проверка ещё не запускалась</span><button id="run-diagnostics" class="secondary-button compact-button" type="button">Проверить всё</button></div>
</header>
<div class="diagnostic-intro">
<span class="diagnostic-pulse" aria-hidden="true"></span>
<p>Здесь видны только ответы текущей авторизованной сессии. Проверка не меняет данные и помогает отличить отсутствие контекста от недоступности сервиса.</p>
</div>
<div id="diagnostic-list" class="diagnostic-list" aria-live="polite" aria-busy="false">
<p class="register-empty">Нажмите «Проверить всё», чтобы получить актуальный снимок.</p>
</div>
</section>
<section class="advisor-panel" aria-labelledby="advisor-title" data-page="diagnostics">
<section class="advisor-panel" aria-labelledby="advisor-title">
<div class="advisor-intro">
<p>COGNITIVE / ADVISORY V1</p>
<h2 id="advisor-title">Спросить Домового</h2>
@@ -170,7 +120,7 @@
</div>
</section>
<section id="operations" class="register-panel" aria-labelledby="register-title" data-page="operations">
<section class="register-panel" aria-labelledby="register-title">
<header>
<div><p>Continuity / ближайшие</p><h2 id="register-title">Реестр ожиданий</h2></div>
<span id="prospective-count">0 ЗАПИСЕЙ</span>
@@ -180,7 +130,7 @@
</div>
</section>
<section class="activity-panel" aria-labelledby="activity-title" data-page="operations">
<section class="activity-panel" aria-labelledby="activity-title">
<header>
<div><p>Commit / владелец</p><h2 id="activity-title">Журнал действий</h2></div>
<div class="activity-tools">
@@ -193,7 +143,7 @@
</div>
</section>
<section id="learning" class="learning-panel" aria-labelledby="learning-title" data-page="learning">
<section class="learning-panel" aria-labelledby="learning-title">
<header>
<div><p>Learning / evidence</p><h2 id="learning-title">Опыт и reflection</h2></div>
<div class="activity-tools">
@@ -217,20 +167,7 @@
</div>
</section>
<section id="settings" class="settings-panel" aria-labelledby="settings-title" data-page="settings">
<header class="panel-heading">
<div><p>Локальные предпочтения</p><h2 id="settings-title">Настройки рабочего кабинета</h2></div>
<span id="settings-saved" class="settings-saved">Сохраняются в этом браузере</span>
</header>
<form id="settings-form" class="settings-grid">
<label class="setting-field"><span>Плотность данных</span><small>Выберите, сколько строк помещается на экране.</small><select id="setting-density"><option value="comfortable">Комфортная</option><option value="compact">Компактная</option></select></label>
<label class="setting-field"><span>Автообновление</span><small>Повторная проверка состояния без изменения данных.</small><select id="setting-refresh"><option value="off">Выключено</option><option value="30">Каждые 30 секунд</option><option value="60">Каждую минуту</option><option value="300">Каждые 5 минут</option></select></label>
<label class="setting-check"><input id="setting-motion" type="checkbox"><span><b>Сократить анимацию</b><small>Оставить спокойный режим для длительной работы.</small></span></label>
<div class="settings-actions"><button id="save-settings" class="primary-button compact-button" type="submit"><span>Сохранить настройки</span><span aria-hidden="true">→</span></button><button id="reset-settings" class="text-button" type="button">Сбросить</button></div>
</form>
</section>
<section class="data-grid" data-page="overview">
<section class="data-grid">
<article class="data-card focus-card">
<header><span class="card-number">01</span><div><p>Situation</p><h2>Активный фокус</h2></div><span id="focus-badge" class="status-badge neutral">НЕ ЗАГРУЖЕН</span></header>
<dl>
@@ -251,7 +188,6 @@
<div><dt>Ревизия</dt><dd id="task-revision">—</dd></div>
</dl>
<p id="task-message" class="card-message">Укажите задачу для проверки относительно активного фокуса.</p>
<div class="id-actions"><button class="id-action" data-use-value="task-value" data-target-id="task-id" type="button">Использовать ID задачи</button><button class="id-action icon-id-action" data-copy-value="task-value" type="button" aria-label="Скопировать ID задачи" title="Скопировать ID">⧉</button></div>
</article>
<article class="data-card prospective-card">
@@ -269,7 +205,6 @@
<div><dt>Ревизия</dt><dd id="prospective-revision">—</dd></div>
</dl>
<p id="prospective-message" class="card-message">Укажите идентификатор ProspectiveItem для проверки владельца и состояния.</p>
<div class="id-actions"><button class="id-action" data-use-value="prospective-value" data-target-id="prospective-id" type="button">Использовать ID ожидания</button><button class="id-action icon-id-action" data-copy-value="prospective-value" type="button" aria-label="Скопировать ID ожидания" title="Скопировать ID">⧉</button></div>
<section class="commit-zone" aria-labelledby="commit-zone-title">
<div class="commit-zone-copy">
<p>Commit / контролируемый переход</p>
@@ -305,7 +240,6 @@
<div><dt>Ревизия</dt><dd id="capability-revision">—</dd></div>
<div><dt>Наблюдалось</dt><dd id="capability-observed">—</dd></div>
</dl>
<div class="id-actions"><button class="id-action" data-use-value="capability-value" data-target-id="capability-id" type="button">Использовать ID возможности</button><button class="id-action icon-id-action" data-copy-value="capability-value" type="button" aria-label="Скопировать ID возможности" title="Скопировать ID">⧉</button></div>
</article>
</section>
</main>
@@ -352,6 +286,6 @@
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
<script src="/assets/console.js?v=20260821-context" defer></script>
<script src="/assets/console.js" defer></script>
</body>
</html>
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000075-creator-chat-primary-surface.json",
"task_id": "DEV-000075",
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
"task_id": "DEV-000074",
"task_revision": 1
}
@@ -1,92 +0,0 @@
{
"task_id": "DEV-000074",
"task_revision": 6,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "1cfbb4ae66e24d514e59dabafb936c35546bd418",
"risk_tier": "E",
"intent": "Make the authenticated Creator Web cabinet understandable and operable for a human by adding section navigation, read-only diagnostics, and browser-local interface preferences without changing domain state or authority boundaries.",
"requirement_ids": [
"Human can move between overview, diagnostics, operations and settings",
"Human can run all currently exposed read-only Creator checks from one diagnostic panel",
"Interface preferences remain local to the browser and never change canonical state",
"Existing explicit confirmation remains required for canonical mutations",
"Desktop and mobile layouts remain usable"
],
"allowed_change_scope": {
"modules": ["cmd/creator-web"],
"paths": [
"cmd/creator-web/static/index.html",
"cmd/creator-web/static/assets/console.css",
"cmd/creator-web/static/assets/console.js",
"cmd/creator-web/server_test.go",
"cmd/creator-web/chat_test.go",
"docs/PROJECT_STATUS.md",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000074-creator-cabinet-operations.json"
]
},
"forbidden_change_scope": {
"paths": [
"architecture/**",
"security/**",
"database/**",
"modules/**",
"contracts/**",
"adapters/**",
"cmd/core-runtime/**",
"cmd/commit-runtime/**",
"cmd/continuity-runtime/**",
"deploy/**"
]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 0,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0,
"new_provider_adapters": 0,
"new_database_owner_roles": 0
},
"contracts": {
"consume": ["existing authenticated Creator gateway endpoints"],
"may_change": []
},
"semantic_owner": "none-interface-adapter",
"canonical_state": [],
"state_transitions": [
"diagnostic request -> read-only gateway observation",
"settings form submit -> browser-local preference update"
],
"side_effects_and_privileges": [
"No new privilege or mutation endpoint",
"No database migration",
"Existing Commit actions retain explicit confirmation dialogs",
"No deployment or neighboring project mutation"
],
"persistence_and_migrations": ["No canonical persistence; localStorage only for interface preferences"],
"failure_retry_restart": ["Diagnostic failures are shown per check without exposing upstream error details"],
"assumptions": [
"Existing Creator API routes remain the source of truth",
"Browser-local settings are disposable and do not affect domain behavior"
],
"acceptance_scenarios": [
"Authenticated cabinet exposes navigation to all four human-facing sections",
"read-only diagnostics checks session, advisory policy, capability, today, attention, actions and learning",
"density, auto-refresh and reduced-motion preferences save and reset locally",
"mobile layout remains readable",
"existing Creator Web tests pass",
"architecture and diff scope checks pass"
],
"required_tests": [
"node --check cmd/creator-web/static/assets/console.js",
"go test ./cmd/creator-web",
"go test ./...",
"go vet ./...",
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
"go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
"go run ./cmd/devctl check-architecture",
"git diff --check",
"responsive browser inspection"
],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,106 @@
{
"task_id": "DEV-000074",
"task_revision": 1,
"architecture_revision": "arch-generation-1",
"base_repo_revision": "fd59b93463bea80459375ecb28864c03ec3efc42",
"risk_tier": "E",
"intent": "Consolidate Codex development memory and cold-start continuity so a fresh session can reconstruct active milestone, boundaries, tests, and next action from repository artifacts only. Do not change Domovoy product behavior; do not change deployment runtime configuration.",
"requirement_ids": [
"DEV-000061 governance continuity",
"FOUNDATION_EXIT_AUDIT checkpoint continuity",
"Cold-start reconstruction from repository only",
"No product behavior regression",
"Durable continuation fields"
],
"allowed_change_scope": {
"modules": [],
"paths": [
"AGENTS.md",
"docs/PROJECT_STATUS.md",
"docs/COLD_START_AND_DURABLE_CHECKPOINT.md",
"docs/evidence/dev-000074-cold-start-reconstruction.md",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json"
]
},
"forbidden_change_scope": {
"paths": [
"architecture/ARCHITECTURE_CONSTITUTION.md",
"architecture/architecture-registry.yaml",
"architecture/architecture-registry.json",
"architecture/adrs/**",
"security/**",
"modules/**",
"contracts/**",
"adapters/**",
"cmd/**",
"deploy/**",
".gitea/**",
"ci/**"
]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 0,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0,
"new_provider_adapters": 0,
"new_database_owner_roles": 0
},
"contracts": {
"consume": [],
"may_change": []
},
"semantic_owner": "governance",
"canonical_state": [
"AGENTS.md contains the durable cold-start startup sequence and checkpoint rules.",
"Checkpoint continuity is preserved in docs/PROJECT_STATUS plus reconstructed durable checkpoint fields."
],
"state_transitions": [
"Fresh session -> read required artifacts -> reconstruct active task and phase",
"Checkpoint update -> clear/record blockers -> define next action",
"Preflight and checks -> produce final continuity status and evidence"
],
"side_effects_and_privileges": [
"No direct external mutation",
"No runtime/runtime-host configuration changes",
"No production actions"
],
"persistence_and_migrations": [
"No database migration",
"No schema or privilege change",
"No canonical ownership change"
],
"failure_retry_restart": [
"If required startup artifacts are missing, return BLOCKED_BY_MISSING_CAPABILITY and update checkpoint.",
"If checkpoint and active task disagree, BLOCKED_BY_SPEC_GAP and require a corrective checkpoint update.",
"Execution-window exhaustion is continuation state, not failure."
],
"assumptions": [
"Foundation architecture and ADR set is already accepted.",
"No production behavior change is authorized in this milestone."
],
"acceptance_scenarios": [
"Fresh session reconstructs active DEV, phase, blockers, and next action from artifacts alone.",
"ACTIVE_TASK points to DEV-000074 task packet after switching.",
"AGENTS cold-start checklist remains practical and non-duplicative.",
"Docs checkpoint explicitly records durable status with explicit evidence references.",
"Architecture/diff checks pass.",
"No changed files outside governance/documentation scope."
],
"required_tests": [
"go test ./...",
"go vet ./...",
"go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
"go run ./cmd/devctl check-architecture",
"go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json"
],
"terminal_gap_states": [
"BLOCKED_BY_SPEC_GAP",
"BLOCKED_BY_ARCHITECTURE_GAP",
"BLOCKED_BY_MISSING_CAPABILITY",
"BLOCKED_BY_CONFLICT",
"REQUIRES_MIGRATION_DECISION"
]
}
@@ -1,47 +0,0 @@
{
"task_id": "DEV-000075",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "39a27102c9665e41f29e85bc8f82a316bbbe3962",
"risk_tier": "E",
"intent": "Make live conversation with Domovoy the public starting surface and keep the Creator operator console on a separate admin route.",
"requirement_ids": [
"Public root opens the human chat",
"Operator console remains available at a separate admin route",
"Existing /chat route remains compatible",
"Chat does not expose provider, model, binding, session or workspace internals"
],
"allowed_change_scope": {
"modules": ["cmd/creator-web"],
"paths": [
"cmd/creator-web/server.go",
"cmd/creator-web/server_test.go",
"cmd/creator-web/chat_test.go",
"cmd/creator-web/static/chat.html",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000075-creator-chat-primary-surface.json"
]
},
"forbidden_change_scope": {
"paths": ["architecture/**", "security/**", "database/**", "modules/**", "contracts/**", "adapters/**", "deploy/**"]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 0,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0,
"new_provider_adapters": 0,
"new_database_owner_roles": 0
},
"contracts": {"consume": ["existing /chat and authenticated Creator endpoints"], "may_change": []},
"semantic_owner": "none-interface-adapter",
"canonical_state": [],
"state_transitions": ["GET / -> chat surface", "GET /admin -> operator console surface"],
"side_effects_and_privileges": ["No new privilege", "No canonical mutation", "No model/provider routing change"],
"persistence_and_migrations": ["No persistence or migration"],
"failure_retry_restart": ["Existing chat failure behavior remains unchanged"],
"assumptions": ["/chat is the existing human chat surface", "The operator console may be reached at /admin"],
"acceptance_scenarios": ["Root serves chat", "Admin serves console", "Chat compatibility remains", "Internal IDs remain absent from chat HTML"],
"required_tests": ["go test ./cmd/creator-web", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
+76
View File
@@ -0,0 +1,76 @@
# Cold start reconstruction and durable checkpoint guidance
## 1) Fresh session reconstruction (no chat dependency)
Any new Codex session must reconstruct active state using repository artifacts only:
1. `git status`, `git rev-parse --abbrev-ref HEAD`, `git log -1 --oneline`
2. `AGENTS.md`
3. `architecture/ARCHITECTURE_CONSTITUTION.md`
4. `architecture/CORE_SEMANTIC_MODEL_V1.md`
5. `architecture/architecture-registry.yaml`
6. `architecture/architecture-registry.json`
7. `development/CODEX_DEVELOPMENT_CONTRACT.md`
8. `dev/ACTIVE_TASK.json`
9. active task packet referenced by `dev/ACTIVE_TASK.json`
10. relevant accepted ADRs
11. `docs/PROJECT_STATUS.md`
12. relevant evidence artifacts in `docs/evidence/**` and tests configured for current task
The objective is to restore:
- what Domovoy is and current implementation state;
- active DEV/task and scope;
- architecture boundaries and governance stack;
- last verified commit and next action.
No external chat context is required.
## 2) Canonical durable checkpoint fields
Current work checkpoints are represented through:
- `docs/PROJECT_STATUS.md` checkpoint blocks per DEV-id.
- `dev/ACTIVE_TASK.json` for active packet identity.
- active task packet fields and scope.
Required conceptual fields:
- `STATUS`
- `CURRENT_PHASE`
- `COMPLETED`
- `IN_PROGRESS`
- `NOT_STARTED`
- `BLOCKERS`
- `IMPORTANT_FINDINGS`
- `EVIDENCE_OBTAINED`
- `LAST_VERIFIED_COMMIT`
- `NEXT_ACTION`
No single file must hold all facts; continuity is reconstructed from the three authoritative artifacts above plus repository evidence.
## 3) Practical continuity checks for a fresh session
The session is recoverable if it can, from the repository alone, answer:
- Which task is active and where to continue?
- What phase is current (`NOT_STARTED` / `IN_PROGRESS` / `COMPLETED`)?
- What blockers and assumptions are known?
- What evidence was last verified and where is it stored?
- What exact next action is safe to perform next?
Any ambiguous active state should be treated as blocked and converted into an explicit checkpoint update rather than implicit inference.
## 4) Deployment continuity rule
- Bootstrap config should remain minimal and task-locating.
- Live canonical configuration belongs to canonical ownership and project artifacts, not ambient shell session state.
- Missing runtime assumptions must fail early during startup checks, not defer silently to manual behavior.
## 5) Evidence expectations for this task family (DEV-000074)
- `docs/PROJECT_STATUS.md` checkpoint updated for DEV-000074.
- `AGENTS.md` contains the startup and checkpoint governance.
- `dev/ACTIVE_TASK.json` points to the active DEV-000074 packet.
- Evidence artifact file or command output captured for cold-start reconstruction validation.
+15 -1
View File
@@ -1,6 +1,20 @@
# Assistant project status
Audit date: 2026-08-17. Active implementation: DEV-000072 revision 1.
Audit date: 2026-08-18. Active implementation: DEV-000074.
## DEV-000074 checkpoint
- `DEV_000074_STATUS = COMPLETED`
- `TASK_SCOPE = governance/cold-start continuity`
- `CURRENT_PHASE = CONTEXT_AND_CHECKPOINT_CONSOLIDATION`
- `COMPLETED = AGENTS.md startup/continuity sequence clarified; Cold start reconstruction guide added; task packet created; active task pointer updated; governance checks executed; cold-start reconstruction evidence captured`
- `IN_PROGRESS = none`
- `NOT_STARTED = product behavior, runtime configuration, or architecture migration changes`
- `BLOCKERS = none`
- `IMPORTANT_FINDINGS = chat-only state is insufficient for reconstruction; repository artifacts are now explicitly documented`
- `EVIDENCE_OBTAINED = AGENTS.md, docs/COLD_START_AND_DURABLE_CHECKPOINT.md, dev/ACTIVE_TASK.json, dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json, check output`
- `LAST_VERIFIED_COMMIT = 1c334fc6a4d9bd8c5b8f8f5a7ccebeccf7d98df2`
- `NEXT_ACTION = none (awaiting next approved milestone)`
## DEV-000072 checkpoint
@@ -0,0 +1,153 @@
# DEV-000074 cold-start reconstruction proof
Date: 2026-08-18T00:19:35.0288130+03:00
1) Git context
Branch: codex/dev-000072-semantic-deferral-boundary-v1
HEAD: fd59b93463bea80459375ecb28864c03ec3efc42
origin/main: 11a6a23f6e4f44dd88265a04846419a27f54df98
MergeBase: ce666bbc43842d339e760957e3c59c490ae1c18a
Status:
M AGENTS.md
M dev/ACTIVE_TASK.json
M docs/PROJECT_STATUS.md
?? dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json
?? docs/COLD_START_AND_DURABLE_CHECKPOINT.md
?? docs/evidence/dev-000074-cold-start-reconstruction.md
2) Active task pointer
{
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
"task_id": "DEV-000074",
"task_revision": 1
}
3) Active task file exists
True
4) DEV-000074 packet
{
"task_id": "DEV-000074",
"task_revision": 1,
"architecture_revision": "arch-generation-1",
"base_repo_revision": "fd59b93463bea80459375ecb28864c03ec3efc42",
"risk_tier": "E",
"intent": "Consolidate Codex development memory and cold-start continuity so a fresh session can reconstruct active milestone, boundaries, tests, and next action from repository artifacts only. Do not change Domovoy product behavior; do not change deployment runtime configuration.",
"requirement_ids": [
"DEV-000061 governance continuity",
"FOUNDATION_EXIT_AUDIT checkpoint continuity",
"Cold-start reconstruction from repository only",
"No product behavior regression",
"Durable continuation fields"
],
"allowed_change_scope": {
"modules": [],
"paths": [
"AGENTS.md",
"docs/PROJECT_STATUS.md",
"docs/COLD_START_AND_DURABLE_CHECKPOINT.md",
"docs/evidence/dev-000074-cold-start-reconstruction.md",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json"
]
},
"forbidden_change_scope": {
"paths": [
"architecture/ARCHITECTURE_CONSTITUTION.md",
"architecture/architecture-registry.yaml",
"architecture/architecture-registry.json",
"architecture/adrs/**",
"security/**",
"modules/**",
"contracts/**",
"adapters/**",
"cmd/**",
"deploy/**",
".gitea/**",
"ci/**"
]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 0,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0,
"new_provider_adapters": 0,
"new_database_owner_roles": 0
},
"contracts": {
"consume": [],
"may_change": []
},
"semantic_owner": "governance",
"canonical_state": [
"AGENTS.md contains the durable cold-start startup sequence and checkpoint rules.",
"Checkpoint continuity is preserved in docs/PROJECT_STATUS plus reconstructed durable checkpoint fields."
],
"state_transitions": [
"Fresh session -> read required artifacts -> reconstruct active task and phase",
"Checkpoint update -> clear/record blockers -> define next action",
"Preflight and checks -> produce final continuity status and evidence"
],
"side_effects_and_privileges": [
"No direct external mutation",
"No runtime/runtime-host configuration changes",
"No production actions"
],
"persistence_and_migrations": [
"No database migration",
"No schema or privilege change",
"No canonical ownership change"
],
"failure_retry_restart": [
"If required startup artifacts are missing, return BLOCKED_BY_MISSING_CAPABILITY and update checkpoint.",
"If checkpoint and active task disagree, BLOCKED_BY_SPEC_GAP and require a corrective checkpoint update.",
"Execution-window exhaustion is continuation state, not failure."
],
"assumptions": [
"Foundation architecture and ADR set is already accepted.",
"No production behavior change is authorized in this milestone."
],
"acceptance_scenarios": [
"Fresh session reconstructs active DEV, phase, blockers, and next action from artifacts alone.",
"ACTIVE_TASK points to DEV-000074 task packet after switching.",
"AGENTS cold-start checklist remains practical and non-duplicative.",
"Docs checkpoint explicitly records durable status with explicit evidence references.",
"Architecture/diff checks pass.",
"No changed files outside governance/documentation scope."
],
"required_tests": [
"go test ./...",
"go vet ./...",
"go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
"go run ./cmd/devctl check-architecture",
"go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json"
],
"terminal_gap_states": [
"BLOCKED_BY_SPEC_GAP",
"BLOCKED_BY_ARCHITECTURE_GAP",
"BLOCKED_BY_MISSING_CAPABILITY",
"BLOCKED_BY_CONFLICT",
"REQUIRES_MIGRATION_DECISION"
]
}
5) Required startup files
AGENTS.md => True
architecture/ARCHITECTURE_CONSTITUTION.md => True
architecture/CORE_SEMANTIC_MODEL_V1.md => True
architecture/architecture-registry.yaml => True
architecture/architecture-registry.json => True
development/CODEX_DEVELOPMENT_CONTRACT.md => True
docs/PROJECT_STATUS.md => True
docs/COLD_START_AND_DURABLE_CHECKPOINT.md => True
6) Required checks executed
go test ./... => PASS
go vet ./... => PASS
go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json => PASS
go run ./cmd/devctl check-architecture => PASS
go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json => PASS
go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json => PASS
@@ -0,0 +1,166 @@
[
{
"id": "A",
"status": 200,
"message": "ты кто",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "86fb129b40d2f2fbb4ab798d863bf35d",
"speech_act": "QUESTION",
"cognition": "COMPLETED",
"clarification": "NOT_REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?"
},
{
"id": "B",
"status": 200,
"message": "сколько будет 2 + 2",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "570663950030add34f71cedb24f4256d",
"speech_act": "QUESTION",
"cognition": "COMPLETED",
"clarification": "NOT_REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "2 + 2 equals 4."
},
{
"id": "C",
"status": 200,
"message": "как с утра",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "008192deff274c8ab7678c180ab7e04d",
"speech_act": "QUESTION",
"cognition": "COMPLETED",
"clarification": "NOT_REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день."
},
{
"id": "D",
"status": 200,
"message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "4607460cd39d273d99ae50a7d8315517",
"speech_act": "QUESTION",
"cognition": "not_requested",
"clarification": "REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу"
},
{
"id": "E",
"message": "исправь прошлую формулировку",
"status": 502,
"body": "chat unavailable\n"
},
{
"id": "F",
"message": "обратись к модели gemma4 и спроси сколько звезд на небе",
"status": 502,
"body": "chat unavailable\n"
},
{
"id": "G",
"status": 200,
"message": "просто напомни, что нужно проверить",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "89b69b8251258a689f38de98552cbed4",
"speech_act": "QUESTION",
"cognition": "not_requested",
"clarification": "REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут."
},
{
"id": "H",
"status": 200,
"message": "Запомни тебя зовут Домовой, я тебя буду звать Домовым",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "0c3bcf9b8f423e93b1b255aa33a557db",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
},
{
"id": "H2",
"status": 200,
"message": "для меня ты Домовой",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "7847420f28c85894400c6a5be3e760d0",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
},
{
"id": "H3",
"status": 200,
"message": "давай я буду звать тебя Домовым",
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
"utterance_id": "085e4194a61cbfd097118553342e063a",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"project_provenance": "",
"active_focus": "",
"capability_status": "",
"capability_kind": "",
"delegation_id": "",
"delegation_state": "",
"work_planned": false,
"reminder_scheduled": false,
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
}
]
@@ -0,0 +1,184 @@
[
{
"id": "A",
"status": 200,
"message": "ты кто",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "7d14f020fc2134f0ba995c35ebe31523",
"speech_act": "QUESTION",
"cognition": "COMPLETED",
"clarification": "NOT_REQUIRED",
"reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "B",
"status": 200,
"message": "сколько будет 2 + 2",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "0a739b4810420e0eb0615d8d4cd4b60b",
"speech_act": "QUESTION",
"cognition": "COMPLETED",
"clarification": "NOT_REQUIRED",
"reply": "2 + 2 equals 4.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "C",
"status": 200,
"message": "как с утра",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "4463c0cc31a552a23c0c0ba4eff06554",
"speech_act": "QUESTION",
"cognition": "COMPLETED",
"clarification": "NOT_REQUIRED",
"reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "D",
"status": 200,
"message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "c80c1eb2ce08edca3add568825c444d7",
"speech_act": "QUESTION",
"cognition": "not_requested",
"clarification": "REQUIRED",
"reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "EXPLICIT",
"active_focus": "УПО"
},
{
"id": "E",
"status": 502,
"message": "исправь прошлую формулировку",
"error": "request_failed"
},
{
"id": "F",
"status": 502,
"message": "обратись к модели gemma4 и спроси сколько звезд на небе",
"error": "request_failed"
},
{
"id": "G",
"status": 200,
"message": "просто напомни, что нужно проверить",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "0519382bed09ae5ea9c9a3b2f2af2641",
"speech_act": "QUESTION",
"cognition": "not_requested",
"clarification": "REQUIRED",
"reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "H",
"status": 200,
"message": "Запомни тебя зовут Домовой",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "aae512074bf81adb405c0c80331c9096",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "H2",
"status": 200,
"message": "для меня ты Домовой",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "78a171bb2f405e72f98f3b19d7422514",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "H3",
"status": 200,
"message": "давай я буду звать тебя Домовым",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "382bd2310ed4a86f671f02c45afe3aee",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
},
{
"id": "H4",
"status": 200,
"message": "Вернемся к УПО",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "8cb57dcf2599947673d1c1a4bb154266",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"reply": "Фокус переключён: УПО.",
"work": false,
"capability_status": "",
"capability_kind": "",
"delegation_state": "",
"project_provenance": "EXPLICIT",
"active_focus": "УПО"
},
{
"id": "I",
"status": 200,
"message": "помоги ответить на письмо бухгалтеру",
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
"utterance_id": "ebeacce8340779490936e702b67543fa",
"speech_act": "ASSERTION",
"cognition": "not_requested",
"clarification": "NOT_REQUIRED",
"reply": "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения.",
"work": false,
"capability_status": "UNAVAILABLE",
"capability_kind": "SEND_EMAIL",
"delegation_state": "",
"project_provenance": "",
"active_focus": ""
}
]
+23
View File
@@ -125,6 +125,10 @@ func BuildDeterministicCandidate(message string, corrects interaction.HumanUtter
tokens := semanticTokens(message)
hasQuestionMark := strings.Contains(message, "?")
scores := semanticScores(tokens, hasQuestionMark, corrects != "")
if isIdentityNamingPreferenceDirective(tokens) {
scores[interaction.SpeechActDirective] += 5
scores[interaction.SpeechActPreference] += 4
}
primary, primaryScore := topAct(scores)
secondary, secondaryScore := secondAct(scores, primary)
if (startsWithModalRequest(tokens) || startsWithExplanatoryRequest(tokens, "расска", "раска", "объяс", "опиш", "покаж", "подскаж")) &&
@@ -170,6 +174,22 @@ func BuildDeterministicCandidate(message string, corrects interaction.HumanUtter
return candidate
}
func isIdentityNamingPreferenceDirective(tokens []string) bool {
if !containsToken(tokens, "ты") && !containsToken(tokens, "тебя") && !containsToken(tokens, "тебе") && !containsToken(tokens, "вам") {
return false
}
if containsStemInTokens(tokens, "запомни", "назови", "назвать", "назвав", "зови", "название", "называ", "обраща", "обращай", "обращайся", "звать", "зов", "зовут") {
return true
}
if containsStemInTokens(tokens, "домов", "assist", "ассист", "assistant", "assistant") {
return true
}
if containsStemInTokens(tokens, "буду", "будешь") && containsStemInTokens(tokens, "звать", "обращ", "называть") {
return true
}
return false
}
func semanticTokens(message string) []string {
lower := strings.ToLower(strings.TrimSpace(message))
replacer := strings.NewReplacer(".", " ", ",", " ", "!", " ", "?", " ", ":", " ", ";", " ", "(", " ", ")", " ", "\"", " ", "'", " ", "«", " ", "»", " ", "+", " + ", "-", " - ", "/", " / ")
@@ -369,6 +389,9 @@ func applicabilityFor(primary, secondary interaction.SpeechAct, primaryScore, se
}
func needsCognitionForCandidate(primary, secondary interaction.SpeechAct, tokens []string) bool {
if primary == interaction.SpeechActDirective && secondary == interaction.SpeechActPreference && isIdentityNamingPreferenceDirective(tokens) {
return false
}
if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate {
return true
}
+19
View File
@@ -93,6 +93,25 @@ func TestDeterministicCandidateTreatsModalExplanationRequestsAsCognitionWorthWit
}
}
func TestDeterministicCandidateClassifiesIdentityNamingPreferenceAsDirective(t *testing.T) {
for _, message := range []string{
"Запомни тебя зовут Домовой, я тебя буду звать Домовым",
"я буду звать тебя Домовым",
"давай я буду звать тебя Домовой",
"теперь буду обращаться к тебе как к Домовой",
"запомни, я называю тебя Домовым",
"для меня ты Домовой",
} {
candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli())
if candidate.PrimarySpeechAct != interaction.SpeechActDirective {
t.Fatalf("expected directive for %q, got %+v", message, candidate)
}
if candidate.NeedsCognition {
t.Fatalf("expected no cognition for naming preference by default for %q, got %+v", message, candidate)
}
}
}
func TestDeterministicCandidateTreatsModelRequestAsDirectiveNeedingCognition(t *testing.T) {
candidate := BuildDeterministicCandidate("обратись к модели gemma4 и спроси сколько звезд на небе", "", time.Unix(10, 0).UnixMilli())
if candidate.PrimarySpeechAct != interaction.SpeechActDirective {
+5 -1
View File
@@ -30,6 +30,10 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
deterministicCandidate := BuildDeterministicCandidate(utterance.Content, utterance.CorrectsUtteranceID, millis)
deterministicCandidate.UtteranceID = utterance.ID
selectedCandidate := deterministicCandidate
assertionVerification := string(epistemics.VerificationNotApplicable)
if selectedCandidate.PrimarySpeechAct == interaction.SpeechActAssertion {
assertionVerification = string(epistemics.VerificationUnverified)
}
result := SemanticResult{
Interpretation: interaction.UtteranceInterpretation{
ID: interaction.InterpretationID(interpretationID),
@@ -38,7 +42,7 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
SecondarySpeechAct: selectedCandidate.SecondarySpeechAct,
ConfidenceMillis: selectedCandidate.ConfidenceMillis,
Source: string(selectedCandidate.Source),
AssertionVerification: string(epistemics.VerificationUnverified),
AssertionVerification: assertionVerification,
RequestedOutcome: selectedCandidate.RequestedOutcome,
Clarification: selectedCandidate.Clarification,
NeedsCognition: selectedCandidate.NeedsCognition,
@@ -66,6 +66,16 @@ func TestInterpretUtteranceQuestionAboutCurrentWorkUsesFocus(t *testing.T) {
}
}
func TestInterpretUtteranceIdentityNamingPreferenceNotAssertion(t *testing.T) {
result := interpretForTest(t, "Запомни тебя зовут Домовой, я тебя буду звать Домовым", nil, "")
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
t.Fatalf("expected directive for naming preference, got %s", result.Interpretation.SpeechAct)
}
if result.Interpretation.AssertionVerification == "UNVERIFIED" {
t.Fatalf("expected no assertion semantics path for naming preference, got %#v", result.Interpretation)
}
}
func TestInterpretUtteranceInheritedFocusForThere(t *testing.T) {
result := interpretForTest(t, "Добавь туда новую настройку.", activeFocus("assistant", "Assistant"), "")
if len(result.Projects) != 1 || result.Projects[0].ProjectID != "assistant" || result.Projects[0].Provenance != situation.ProjectResolutionInheritedFocus {