feat: load packet logs from configured source
This commit is contained in:
@@ -15,7 +15,10 @@ func main() {
|
|||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
server := mcpserver.NewServer()
|
server, err := mcpserver.NewServerFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("configure isphere mcp server: %v", err)
|
||||||
|
}
|
||||||
if err := server.Run(ctx, &mcp.StdioTransport{}); err != nil {
|
if err := server.Run(ctx, &mcp.StdioTransport{}); err != nil {
|
||||||
log.Fatalf("isphere mcp server stopped: %v", err)
|
log.Fatalf("isphere mcp server stopped: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ Remote base before local roadmap commits: `b2d839e Merge branch 'codex/n15-repor
|
|||||||
- `win_helper_dump_uia`
|
- `win_helper_dump_uia`
|
||||||
- `isphere_receive_messages`
|
- `isphere_receive_messages`
|
||||||
- C# `ISphereWinHelper` exists under `native/ISphereWinHelper` and uses the `isphere.helper.v1` stdin/stdout JSON contract.
|
- C# `ISphereWinHelper` exists under `native/ISphereWinHelper` and uses the `isphere.helper.v1` stdin/stdout JSON contract.
|
||||||
- `scripts/verify-win-helper.ps1` and `scripts/verify-go-mcp.ps1` provide repeatable local checks for the helper, five-tool MCP surface, and empty-source `isphere_receive_messages` smoke call.
|
- `scripts/verify-win-helper.ps1` and `scripts/verify-go-mcp.ps1` provide repeatable local checks for the helper, five-tool MCP surface, and default empty-source `isphere_receive_messages` smoke call.
|
||||||
- N12-pre and N12R documents define safe offline evidence intake and internal-sandbox live UIA capture procedures.
|
- N12-pre and N12R documents define safe offline evidence intake and internal-sandbox live UIA capture procedures.
|
||||||
- N13/N14/N15 produced selector catalog/report validation infrastructure, but those are supporting validation assets only.
|
- N13/N14/N15 produced selector catalog/report validation infrastructure, but those are supporting validation assets only.
|
||||||
|
|
||||||
## What cannot be claimed externally
|
## What cannot be claimed externally
|
||||||
|
|
||||||
- The project has not yet implemented business MCP tools for contact search, group search, message sending, file receiving, or file sending; `isphere_receive_messages` is registered but still lacks a production loader/configuration path.
|
- The project has not yet implemented business MCP tools for contact search, group search, message sending, file receiving, or file sending; `isphere_receive_messages` has an operator-local encrypted log-file loader but not a production ingestion path.
|
||||||
- The project has not completed production iSphere integration.
|
- The project has not completed production iSphere integration.
|
||||||
- The project has not proven a stable real message source for the full business workflow beyond decrypted PacketReader log evidence and synthetic/redacted tests.
|
- The project has not proven a stable real message source for the full business workflow beyond decrypted PacketReader log evidence and synthetic/redacted tests.
|
||||||
- The project has not implemented real message sending or real file upload.
|
- The project has not implemented real message sending or real file upload.
|
||||||
@@ -40,7 +40,7 @@ Remote base before local roadmap commits: `b2d839e Merge branch 'codex/n15-repor
|
|||||||
- The helper protocol boundary is explicit and keeps Windows/UIA work separate from the Go MCP service layer.
|
- The helper protocol boundary is explicit and keeps Windows/UIA work separate from the Go MCP service layer.
|
||||||
- The current code can support future MCP business tools by routing low-level Windows/UIA fallback work through a bounded helper layer.
|
- The current code can support future MCP business tools by routing low-level Windows/UIA fallback work through a bounded helper layer.
|
||||||
- The project has repeatable verification scripts for the helper/MCP foundation and the current five-tool surface.
|
- The project has repeatable verification scripts for the helper/MCP foundation and the current five-tool surface.
|
||||||
- The project now has a corrected business contract and plan for core MCP communication tools, plus a registered first read tool for receiving messages from a future configured source.
|
- The project now has a corrected business contract and plan for core MCP communication tools, plus a registered first read tool that can use `ISPHERE_PACKET_LOG_FILE` for operator-local encrypted PacketReader log lines.
|
||||||
|
|
||||||
## N13/N14/N15 positioning
|
## N13/N14/N15 positioning
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ N13/N14/N15 are pre-business validation results. They can help identify UI eleme
|
|||||||
|
|
||||||
## Current loop
|
## Current loop
|
||||||
|
|
||||||
Current loop: `Stage C / Loop C4 - operator verification and docs`.
|
Current loop: `Stage C / Loop C6 - fixture-backed operator smoke for configured receive source`.
|
||||||
|
|
||||||
Plan file: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`.
|
Plan file: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`.
|
||||||
|
|
||||||
@@ -64,12 +64,14 @@ Current loop output so far:
|
|||||||
1. C1: `internal/isphere/logcodec.DecryptLine` and `internal/isphere/packetlog.ParseMessageLog` for redacted PacketReader message logs.
|
1. C1: `internal/isphere/logcodec.DecryptLine` and `internal/isphere/packetlog.ParseMessageLog` for redacted PacketReader message logs.
|
||||||
2. C2: `internal/isphere.EncryptedPacketLogSource.ReceiveMessages` source abstraction with synthetic/redacted encrypted fixtures.
|
2. C2: `internal/isphere.EncryptedPacketLogSource.ReceiveMessages` source abstraction with synthetic/redacted encrypted fixtures.
|
||||||
3. C3: `isphere_receive_messages` MCP tool registration and server tool list update.
|
3. C3: `isphere_receive_messages` MCP tool registration and server tool list update.
|
||||||
4. C4: updating repeatable operator verification and docs for the five-tool surface.
|
4. C4: repeatable operator verification and docs for the five-tool surface.
|
||||||
|
5. C5: safe `ISPHERE_PACKET_LOG_FILE` loader for operator-local encrypted PacketReader log lines.
|
||||||
|
6. C6: fixture-backed operator smoke for configured receive source.
|
||||||
|
|
||||||
## Next business mainline
|
## Next business mainline
|
||||||
|
|
||||||
1. Finish Stage C Loop C4: verify and document the five-tool MCP surface.
|
1. Finish Stage C Loop C6: add fixture-backed configured-source operator smoke.
|
||||||
2. Implement the next loop: safe configurable source loader or fixture path for `isphere_receive_messages`, without committing raw evidence.
|
2. Rewrite the next loop around either production-safe source configuration hardening or the next read capability, based on C6 results.
|
||||||
3. Add contact/group/file read tools after their source decisions are recorded.
|
3. Add contact/group/file read tools after their source decisions are recorded.
|
||||||
4. Add `isphere_send_message` and `isphere_send_file` after source discovery and connector selection.
|
4. Add `isphere_send_message` and `isphere_send_file` after source discovery and connector selection.
|
||||||
5. Keep UIA selector/report work as fallback support, not as the primary roadmap.
|
5. Keep UIA selector/report work as fallback support, not as the primary roadmap.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This runbook is for the first Go MCP phase of `isphere-ai-bridge`. It lets an operator build and verify the Windows helper and the Go MCP server without reading the source code.
|
This runbook is for the first Go MCP phase of `isphere-ai-bridge`. It lets an operator build and verify the Windows helper and the Go MCP server without reading the source code.
|
||||||
|
|
||||||
Current scope: expose four WinHelper observation operations plus the first log-backed business read tool, `isphere_receive_messages`, through Go MCP. The receive tool is registered with an empty default source until a safe evidence loader is wired.
|
Current scope: expose four WinHelper observation operations plus the first log-backed business read tool, `isphere_receive_messages`, through Go MCP. The receive tool is registered with an empty default source unless `ISPHERE_PACKET_LOG_FILE` points to an operator-local encrypted PacketReader log-line file.
|
||||||
|
|
||||||
## 1. Prerequisites
|
## 1. Prerequisites
|
||||||
|
|
||||||
@@ -96,7 +96,25 @@ The verification confirms:
|
|||||||
- Verification uses synthetic/local helper checks and does not load raw N12-pre evidence.
|
- Verification uses synthetic/local helper checks and does not load raw N12-pre evidence.
|
||||||
- Contact/group/file/search/send business tools remain later-stage work.
|
- Contact/group/file/search/send business tools remain later-stage work.
|
||||||
|
|
||||||
## 6. Configure MCP client command
|
## 6. Configure optional message source
|
||||||
|
|
||||||
|
By default, `isphere_receive_messages` is registered but uses an empty source, so it returns:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"messages":[]}
|
||||||
|
```
|
||||||
|
|
||||||
|
To point the server at a local encrypted PacketReader log-line file, set:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:ISPHERE_PACKET_LOG_FILE = "E:\coding\codex\isphere-ai-bridge\runs\offline-evidence-intake\<evidence-id>\packet-reader-lines.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
The file must contain one encrypted Base64 log line per line. Blank lines are ignored. Keep this file under ignored `runs/` paths or another operator-local location; do not commit raw logs or decrypted message content.
|
||||||
|
|
||||||
|
Then start the MCP server normally. The command entry point uses `ISPHERE_PACKET_LOG_FILE` if it is set; the repeatable verification script clears it so the default smoke test remains deterministic.
|
||||||
|
|
||||||
|
## 7. Configure MCP client command
|
||||||
|
|
||||||
After building a stable binary, configure your MCP client to run the Go MCP executable as a stdio server.
|
After building a stable binary, configure your MCP client to run the Go MCP executable as a stdio server.
|
||||||
|
|
||||||
@@ -131,7 +149,7 @@ If you used `go build ./cmd/isphere-mcp` without `-o`, point the command to:
|
|||||||
E:\coding\codex\isphere-ai-bridge\isphere-mcp.exe
|
E:\coding\codex\isphere-ai-bridge\isphere-mcp.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. Allowed tools
|
## 8. Allowed tools
|
||||||
|
|
||||||
These five tools are currently allowed:
|
These five tools are currently allowed:
|
||||||
|
|
||||||
@@ -151,7 +169,7 @@ Allowed parameters:
|
|||||||
- `win_helper_dump_uia`: `hwnd`, `max_depth`, `include_text`, `max_children` only.
|
- `win_helper_dump_uia`: `hwnd`, `max_depth`, `include_text`, `max_children` only.
|
||||||
- `isphere_receive_messages`: `limit` only.
|
- `isphere_receive_messages`: `limit` only.
|
||||||
|
|
||||||
## 8. Current tool surface and next-stage route
|
## 9. Current tool surface and next-stage route
|
||||||
|
|
||||||
The current runbook verifies five tools:
|
The current runbook verifies five tools:
|
||||||
|
|
||||||
@@ -161,11 +179,11 @@ The current runbook verifies five tools:
|
|||||||
- `win_helper_dump_uia`
|
- `win_helper_dump_uia`
|
||||||
- `isphere_receive_messages`
|
- `isphere_receive_messages`
|
||||||
|
|
||||||
`isphere_receive_messages` currently has parser/source/tool registration and an empty default server source. Raw N12-pre evidence remains under ignored `runs/` paths and is not committed. The next implementation slice should wire a safe configurable loader or fixture path before claiming production message ingestion.
|
`isphere_receive_messages` currently has parser/source/tool registration and an empty default server source. Raw N12-pre evidence remains under ignored `runs/` paths and is not committed. The current command entry point can read an operator-local encrypted PacketReader log-line file via `ISPHERE_PACKET_LOG_FILE`; this is still not a production ingestion claim because raw evidence remains local and uncommitted.
|
||||||
|
|
||||||
Core business tools for contacts, groups, messages, and files are defined in `docs/mcp-core-tools-contract.md`. Send/file capabilities enter the roadmap through that contract after source discovery, connector selection, preview metadata, execution metadata, and audit records are in place.
|
Core business tools for contacts, groups, messages, and files are defined in `docs/mcp-core-tools-contract.md`. Send/file capabilities enter the roadmap through that contract after source discovery, connector selection, preview metadata, execution metadata, and audit records are in place.
|
||||||
|
|
||||||
## 9. Real-login UIA capture procedure
|
## 10. Real-login UIA capture procedure
|
||||||
|
|
||||||
This is a later real-login capture gate used to collect live UI structure for connector work.
|
This is a later real-login capture gate used to collect live UI structure for connector work.
|
||||||
|
|
||||||
@@ -206,7 +224,7 @@ Procedure:
|
|||||||
|
|
||||||
8. Review and redact any sensitive content before sharing reports.
|
8. Review and redact any sensitive content before sharing reports.
|
||||||
|
|
||||||
## 10. If this outer-network environment cannot log in
|
## 11. If this outer-network environment cannot log in
|
||||||
|
|
||||||
iSphere is internal-network only. If the current repository environment is outside that network, use two separate routes:
|
iSphere is internal-network only. If the current repository environment is outside that network, use two separate routes:
|
||||||
|
|
||||||
@@ -237,7 +255,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\validate-n12r-retu
|
|||||||
|
|
||||||
Keep the validation report outside `$captureRoot`; the returned package is evidence input and should remain unchanged.
|
Keep the validation report outside `$captureRoot`; the returned package is evidence input and should remain unchanged.
|
||||||
|
|
||||||
## 11. Troubleshooting
|
## 12. Troubleshooting
|
||||||
|
|
||||||
- If C# helper build fails, run `scripts\build-win-helper.ps1` directly and check for missing .NET Framework reference assemblies.
|
- If C# helper build fails, run `scripts\build-win-helper.ps1` directly and check for missing .NET Framework reference assemblies.
|
||||||
- If `win_helper_version` fails, rerun `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-win-helper.ps1` first.
|
- If `win_helper_version` fails, rerun `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-win-helper.ps1` first.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Schema notes: `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md`
|
|||||||
|
|
||||||
| MCP tool | Primary source | Fallback source | Evidence file | Decision status | Next implementation slice |
|
| MCP tool | Primary source | Fallback source | Evidence file | Decision status | Next implementation slice |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| `isphere_receive_messages` | decrypted `PacketReader.ProcessPacket` XMPP `<message>` stanzas via `internal/isphere.EncryptedPacketLogSource` | decrypted `Smark.SendReceive` transport stanzas; decrypted `SaveToDB` `Chat_OnMessageArrived` traces; wrapped `MsgLib.db` after DB wrapper is solved | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | C3 registered and unit-verified; default server source is empty until operator verification/loader wiring | fixture-backed operator verification and docs |
|
| `isphere_receive_messages` | decrypted `PacketReader.ProcessPacket` XMPP `<message>` stanzas via `internal/isphere.EncryptedPacketLogSource` | decrypted `Smark.SendReceive` transport stanzas; decrypted `SaveToDB` `Chat_OnMessageArrived` traces; wrapped `MsgLib.db` after DB wrapper is solved | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | C5 env loader wired through ISPHERE_PACKET_LOG_FILE; default source remains empty when unset | fixture-backed configured-source operator smoke |
|
||||||
| `isphere_search_contacts` | decrypted packet/send-receive JIDs and later roster stanzas from `Smark.SendReceive` | `MsgLib.db` contact/conversation tables after DB wrapper is solved; UIA helper source if display names are missing | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#log-decryption-result` | candidate pending parser coverage | second |
|
| `isphere_search_contacts` | decrypted packet/send-receive JIDs and later roster stanzas from `Smark.SendReceive` | `MsgLib.db` contact/conversation tables after DB wrapper is solved; UIA helper source if display names are missing | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#log-decryption-result` | candidate pending parser coverage | second |
|
||||||
| `isphere_search_groups` | decrypted XMPP group/message stanzas if groupchat/group JIDs appear | `MsgLib.db` group/conversation tables after DB wrapper is solved; UIA helper source if group display names are missing | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | candidate pending group stanza evidence | third |
|
| `isphere_search_groups` | decrypted XMPP group/message stanzas if groupchat/group JIDs appear | `MsgLib.db` group/conversation tables after DB wrapper is solved; UIA helper source if group display names are missing | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | candidate pending group stanza evidence | third |
|
||||||
| `isphere_receive_files` | decrypted `PacketReader.ProcessPacket` file-transfer message stanzas plus `zyl\importal\<hash>` cache mapping | decrypted `Smark.SendReceive` and `SaveToDB` traces for file-reference reconciliation | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | candidate pending cache-reference mapping | fourth |
|
| `isphere_receive_files` | decrypted `PacketReader.ProcessPacket` file-transfer message stanzas plus `zyl\importal\<hash>` cache mapping | decrypted `Smark.SendReceive` and `SaveToDB` traces for file-reference reconciliation | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | candidate pending cache-reference mapping | fourth |
|
||||||
|
|||||||
@@ -795,7 +795,7 @@ Implemented and verified in this loop:
|
|||||||
- Configured behavior: env var set to a local text file means the server reads non-empty encrypted Base64 lines from that file into `isphere.EncryptedPacketLogSource{Lines: lines}`.
|
- Configured behavior: env var set to a local text file means the server reads non-empty encrypted Base64 lines from that file into `isphere.EncryptedPacketLogSource{Lines: lines}`.
|
||||||
- Evidence rule: the configured file should live under ignored `runs/` or another operator-local path; the loader must not require or commit raw evidence.
|
- Evidence rule: the configured file should live under ignored `runs/` or another operator-local path; the loader must not require or commit raw evidence.
|
||||||
|
|
||||||
- [ ] **Step 1: Write failing file loader test**
|
- [x] **Step 1: Write failing file loader test**
|
||||||
|
|
||||||
Create `internal/isphere/file_source_test.go` first and run:
|
Create `internal/isphere/file_source_test.go` first and run:
|
||||||
|
|
||||||
@@ -805,23 +805,23 @@ go test ./internal/isphere -run TestLoadEncryptedPacketLogSourceFromFileReadsNon
|
|||||||
|
|
||||||
Expected initial failure: loader function/type does not exist.
|
Expected initial failure: loader function/type does not exist.
|
||||||
|
|
||||||
- [ ] **Step 2: Implement file loader**
|
- [x] **Step 2: Implement file loader**
|
||||||
|
|
||||||
Implement a small loader that reads a UTF-8 text file, trims whitespace, skips blank lines, and returns `EncryptedPacketLogSource{Lines: lines}`. It should return clear path/context errors for missing or unreadable files.
|
Implement a small loader that reads a UTF-8 text file, trims whitespace, skips blank lines, and returns `EncryptedPacketLogSource{Lines: lines}`. It should return clear path/context errors for missing or unreadable files.
|
||||||
|
|
||||||
- [ ] **Step 3: Write failing env-wired server test**
|
- [x] **Step 3: Write failing env-wired server test**
|
||||||
|
|
||||||
Update `internal/mcpserver/server_test.go` with a temp encrypted fixture file and a test that sets `ISPHERE_PACKET_LOG_FILE`, constructs the env-wired server, calls `isphere_receive_messages`, and expects one redacted message. Run the specific test before implementation.
|
Update `internal/mcpserver/server_test.go` with a temp encrypted fixture file and a test that sets `ISPHERE_PACKET_LOG_FILE`, constructs the env-wired server, calls `isphere_receive_messages`, and expects one redacted message. Run the specific test before implementation.
|
||||||
|
|
||||||
- [ ] **Step 4: Implement env-wired server construction**
|
- [x] **Step 4: Implement env-wired server construction**
|
||||||
|
|
||||||
Add `NewServerFromEnv()` or equivalent env-aware construction. Keep `NewServer()` as the empty default for existing tests. Update `cmd/isphere-mcp/main.go` to use the env-aware server.
|
Add `NewServerFromEnv()` or equivalent env-aware construction. Keep `NewServer()` as the empty default for existing tests. Update `cmd/isphere-mcp/main.go` to use the env-aware server.
|
||||||
|
|
||||||
- [ ] **Step 5: Update operator verification/docs**
|
- [x] **Step 5: Update operator verification/docs**
|
||||||
|
|
||||||
Extend `scripts/verify-go-mcp.ps1` so default verification still asserts zero messages when no env var is set. Document optional `ISPHERE_PACKET_LOG_FILE` usage in `docs/go-mcp-runbook.md` and update the status card.
|
Extend `scripts/verify-go-mcp.ps1` so default verification still asserts zero messages when no env var is set. Document optional `ISPHERE_PACKET_LOG_FILE` usage in `docs/go-mcp-runbook.md` and update the status card.
|
||||||
|
|
||||||
- [ ] **Step 6: Run full verification and commit**
|
- [x] **Step 6: Run full verification and commit**
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
@@ -836,6 +836,67 @@ Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build. C
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Loop C5 Result
|
||||||
|
|
||||||
|
Implemented and verified in this loop:
|
||||||
|
|
||||||
|
- `internal/isphere.LoadEncryptedPacketLogSourceFromFile(path)` reads non-empty encrypted Base64 PacketReader log lines from an operator-local text file and returns `EncryptedPacketLogSource{Lines: lines}`.
|
||||||
|
- `internal/mcpserver.NewServerFromEnv()` reads `ISPHERE_PACKET_LOG_FILE` when set, otherwise preserves the empty default receive source.
|
||||||
|
- `cmd/isphere-mcp/main.go` now uses the env-aware server constructor and fails fast on invalid configuration.
|
||||||
|
- `scripts/verify-go-mcp.ps1` now exercises `NewServerFromEnv()` with `ISPHERE_PACKET_LOG_FILE` cleared and confirms the deterministic empty-source behavior.
|
||||||
|
- Docs now explain optional `ISPHERE_PACKET_LOG_FILE` usage and reinforce that raw evidence must remain under ignored/operator-local paths.
|
||||||
|
- Verification completed for C5 with `git diff --check`, `go test ./...`, `go build ./cmd/isphere-mcp`, and `powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1`; the generated root `isphere-mcp.exe` build artifact was removed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Loop C6: Fixture-backed operator smoke for configured receive source
|
||||||
|
|
||||||
|
**Goal:** Extend repeatable operator verification so a synthetic encrypted PacketReader fixture file proves `ISPHERE_PACKET_LOG_FILE` can drive `isphere_receive_messages` end-to-end without committing raw evidence.
|
||||||
|
|
||||||
|
**Planned files:**
|
||||||
|
- Modify: `scripts/verify-go-mcp.ps1`
|
||||||
|
- Modify: `docs/go-mcp-runbook.md`
|
||||||
|
- Modify: `docs/current-status-card.md`
|
||||||
|
- Modify: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`
|
||||||
|
|
||||||
|
**Planned behavior:**
|
||||||
|
- Existing default smoke remains: unset `ISPHERE_PACKET_LOG_FILE` returns `messages: []`.
|
||||||
|
- New fixture smoke creates a temporary synthetic/redacted PacketReader plaintext, encrypts it with the known log policy inside the temporary Go harness, writes one encrypted line to a temp file, sets `ISPHERE_PACKET_LOG_FILE` to that temp file, starts `mcpserver.NewServerFromEnv()`, calls `isphere_receive_messages`, and asserts one normalized message.
|
||||||
|
- No raw N12-pre evidence or decrypted user content is written to the repo.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Capture current fixture-smoke gap**
|
||||||
|
|
||||||
|
Run the current verification script and note that it only proves the default empty source, not configured fixture ingestion:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS with `receive_message_count: 0`, but no configured-source proof yet.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update the temporary Go harness**
|
||||||
|
|
||||||
|
Extend the generated harness with a second server session that sets `ISPHERE_PACKET_LOG_FILE` to a temp synthetic encrypted log-line file and asserts one returned message with redacted content.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update docs**
|
||||||
|
|
||||||
|
Update the runbook/status card to say verification covers both default empty source and synthetic configured-source smoke.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run full verification and commit**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check
|
||||||
|
go test ./...
|
||||||
|
go build ./cmd/isphere-mcp
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build. Commit exact C6 paths only and rewrite the next loop from actual results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Self-Review
|
## Self-Review
|
||||||
|
|
||||||
- Spec coverage: the plan follows the user's cyclic requirement: plan first, implement one loop at a time, update the next loop after each result, and ask only on blockers.
|
- Spec coverage: the plan follows the user's cyclic requirement: plan first, implement one loop at a time, update the next loop after each result, and ask only on blockers.
|
||||||
|
|||||||
36
internal/isphere/file_source.go
Normal file
36
internal/isphere/file_source.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package isphere
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadEncryptedPacketLogSourceFromFile(path string) (EncryptedPacketLogSource, error) {
|
||||||
|
trimmedPath := strings.TrimSpace(path)
|
||||||
|
if trimmedPath == "" {
|
||||||
|
return EncryptedPacketLogSource{}, fmt.Errorf("packet log file path is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(trimmedPath)
|
||||||
|
if err != nil {
|
||||||
|
return EncryptedPacketLogSource{}, fmt.Errorf("open packet log file %q: %w", trimmedPath, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
scanner.Buffer(make([]byte, 64*1024), 16*1024*1024)
|
||||||
|
lines := []string{}
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return EncryptedPacketLogSource{}, fmt.Errorf("read packet log file %q: %w", trimmedPath, err)
|
||||||
|
}
|
||||||
|
return EncryptedPacketLogSource{Lines: lines}, nil
|
||||||
|
}
|
||||||
29
internal/isphere/file_source_test.go
Normal file
29
internal/isphere/file_source_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package isphere
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadEncryptedPacketLogSourceFromFileReadsNonEmptyLines(t *testing.T) {
|
||||||
|
path := writePacketLogFileForTest(t, "\n encrypted-line-1 \r\n\r\n\tencrypted-line-2\n")
|
||||||
|
|
||||||
|
got, err := LoadEncryptedPacketLogSourceFromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadEncryptedPacketLogSourceFromFile returned error: %v", err)
|
||||||
|
}
|
||||||
|
want := EncryptedPacketLogSource{Lines: []string{"encrypted-line-1", "encrypted-line-2"}}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("source = %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePacketLogFileForTest(t *testing.T, content string) string {
|
||||||
|
t.Helper()
|
||||||
|
path := t.TempDir() + "\\packet.log"
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
||||||
|
t.Fatalf("write test packet log: %v", err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package mcpserver
|
package mcpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
"isphere-ai-bridge/internal/helperclient"
|
"isphere-ai-bridge/internal/helperclient"
|
||||||
@@ -9,18 +13,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ServerName = "isphere-ai-bridge"
|
ServerName = "isphere-ai-bridge"
|
||||||
ServerTitle = "iSphere AI Bridge"
|
ServerTitle = "iSphere AI Bridge"
|
||||||
ServerVersion = "0.1.0"
|
ServerVersion = "0.1.0"
|
||||||
|
EnvPacketLogFileKey = "ISPHERE_PACKET_LOG_FILE"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer() *mcp.Server {
|
func NewServer() *mcp.Server {
|
||||||
|
return NewServerWithReceiveSource(isphere.EncryptedPacketLogSource{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerFromEnv() (*mcp.Server, error) {
|
||||||
|
var source tools.ReceiveMessagesSource = isphere.EncryptedPacketLogSource{}
|
||||||
|
packetLogFile := strings.TrimSpace(os.Getenv(EnvPacketLogFileKey))
|
||||||
|
if packetLogFile != "" {
|
||||||
|
loaded, err := isphere.LoadEncryptedPacketLogSourceFromFile(packetLogFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("configure %s: %w", EnvPacketLogFileKey, err)
|
||||||
|
}
|
||||||
|
source = loaded
|
||||||
|
}
|
||||||
|
return NewServerWithReceiveSource(source), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerWithReceiveSource(source tools.ReceiveMessagesSource) *mcp.Server {
|
||||||
server := mcp.NewServer(&mcp.Implementation{
|
server := mcp.NewServer(&mcp.Implementation{
|
||||||
Name: ServerName,
|
Name: ServerName,
|
||||||
Title: ServerTitle,
|
Title: ServerTitle,
|
||||||
Version: ServerVersion,
|
Version: ServerVersion,
|
||||||
}, nil)
|
}, nil)
|
||||||
tools.RegisterWinHelperTools(server, helperclient.Client{})
|
tools.RegisterWinHelperTools(server, helperclient.Client{})
|
||||||
tools.RegisterISphereReadTools(server, isphere.EncryptedPacketLogSource{})
|
tools.RegisterISphereReadTools(server, source)
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package mcpserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/des"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -57,3 +62,127 @@ func TestNewServerRegistersN8WinHelperToolsWithoutCallingHelper(t *testing.T) {
|
|||||||
t.Fatalf("server tool names = %#v, want %#v", gotNames, wantNames)
|
t.Fatalf("server tool names = %#v, want %#v", gotNames, wantNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewServerFromEnvUsesPacketLogFile(t *testing.T) {
|
||||||
|
plaintext := `--------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
2026/7/7 15:30:07
|
||||||
|
<message id="msg-env-1" from="sender@imopenfire1-lanzhou/imp_pc_4.1.2.6842" to="receiver@imopenfire1-lanzhou" type="chat">
|
||||||
|
<body>redacted from env</body>
|
||||||
|
<received xmlns="urn:xmpp:receipts" id="receipt-env-1" type="1" stamp="" />
|
||||||
|
<subject>ENV_SOURCE</subject>
|
||||||
|
<isphere xmlns="isphere.im" type="1001" sendtime="1783423807000" version="1" />
|
||||||
|
<readed />
|
||||||
|
</message>`
|
||||||
|
filePath := writeEncryptedPacketLogFileForServerTest(t, plaintext)
|
||||||
|
t.Setenv("ISPHERE_PACKET_LOG_FILE", filePath)
|
||||||
|
|
||||||
|
server, err := NewServerFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewServerFromEnv returned error: %v", err)
|
||||||
|
}
|
||||||
|
session, cleanup := connectServerTestSession(t, server)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
callResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
||||||
|
Name: "isphere_receive_messages",
|
||||||
|
Arguments: map[string]any{"limit": 10},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("call isphere_receive_messages: %v", err)
|
||||||
|
}
|
||||||
|
if callResult.IsError {
|
||||||
|
t.Fatalf("call result is error: %+v", callResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded struct {
|
||||||
|
Messages []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
ConversationID string `json:"conversation_id"`
|
||||||
|
SenderID string `json:"sender_id"`
|
||||||
|
ReceiverID string `json:"receiver_id"`
|
||||||
|
Read bool `json:"read"`
|
||||||
|
} `json:"messages"`
|
||||||
|
}
|
||||||
|
payload, err := json.Marshal(callResult.StructuredContent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal structured content: %v", err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(payload, &decoded); err != nil {
|
||||||
|
t.Fatalf("decode structured content %s: %v", payload, err)
|
||||||
|
}
|
||||||
|
if len(decoded.Messages) != 1 {
|
||||||
|
t.Fatalf("message count = %d, want 1", len(decoded.Messages))
|
||||||
|
}
|
||||||
|
msg := decoded.Messages[0]
|
||||||
|
if msg.ID != "msg-env-1" || msg.Text != "redacted from env" || msg.Subject != "ENV_SOURCE" {
|
||||||
|
t.Fatalf("unexpected message: %+v", msg)
|
||||||
|
}
|
||||||
|
if msg.ConversationID != "sender@imopenfire1-lanzhou|receiver@imopenfire1-lanzhou" {
|
||||||
|
t.Fatalf("conversation id = %q", msg.ConversationID)
|
||||||
|
}
|
||||||
|
if msg.SenderID != "sender@imopenfire1-lanzhou" || msg.ReceiverID != "receiver@imopenfire1-lanzhou" || !msg.Read {
|
||||||
|
t.Fatalf("metadata = %+v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectServerTestSession(t *testing.T, server *mcp.Server) (*mcp.ClientSession, func()) {
|
||||||
|
t.Helper()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errCh <- server.Run(ctx, serverTransport)
|
||||||
|
}()
|
||||||
|
|
||||||
|
client := mcp.NewClient(&mcp.Implementation{Name: "mcpserver-env-test-client", Version: "0.0.0"}, nil)
|
||||||
|
session, err := client.Connect(ctx, clientTransport, nil)
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
t.Fatalf("connect client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
_ = session.Close()
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case <-errCh:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatalf("server did not stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return session, cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEncryptedPacketLogFileForServerTest(t *testing.T, plaintext string) string {
|
||||||
|
t.Helper()
|
||||||
|
path := t.TempDir() + "\\packet-reader.log"
|
||||||
|
line := encryptPacketLogLineForServerTest(t, plaintext)
|
||||||
|
if err := os.WriteFile(path, []byte("\n"+line+"\n"), 0o600); err != nil {
|
||||||
|
t.Fatalf("write encrypted packet log file: %v", err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptPacketLogLineForServerTest(t *testing.T, plaintext string) string {
|
||||||
|
t.Helper()
|
||||||
|
block, err := des.NewCipher([]byte("hyhccdtm"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCipher: %v", err)
|
||||||
|
}
|
||||||
|
plain := padPacketLogLineForServerTest([]byte(plaintext), des.BlockSize)
|
||||||
|
ciphertext := make([]byte, len(plain))
|
||||||
|
iv := []byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}
|
||||||
|
cipher.NewCBCEncrypter(block, iv).CryptBlocks(ciphertext, plain)
|
||||||
|
return base64.StdEncoding.EncodeToString(ciphertext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func padPacketLogLineForServerTest(data []byte, blockSize int) []byte {
|
||||||
|
pad := blockSize - len(data)%blockSize
|
||||||
|
out := append([]byte(nil), data...)
|
||||||
|
for i := 0; i < pad; i++ {
|
||||||
|
out = append(out, byte(pad))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,8 +84,15 @@ func main() {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
if err := os.Unsetenv("ISPHERE_PACKET_LOG_FILE"); err != nil {
|
||||||
|
fail("server/env", err)
|
||||||
|
}
|
||||||
|
|
||||||
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
serverTransport, clientTransport := mcp.NewInMemoryTransports()
|
||||||
server := mcpserver.NewServer()
|
server, err := mcpserver.NewServerFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
fail("server/config", err)
|
||||||
|
}
|
||||||
serverErr := make(chan error, 1)
|
serverErr := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
serverErr <- server.Run(ctx, serverTransport)
|
serverErr <- server.Run(ctx, serverTransport)
|
||||||
@@ -183,6 +190,7 @@ func main() {
|
|||||||
"tools": toolNames,
|
"tools": toolNames,
|
||||||
"helper_name": helperName,
|
"helper_name": helperName,
|
||||||
"receive_message_count": len(messagesValue),
|
"receive_message_count": len(messagesValue),
|
||||||
|
"packet_log_file_configured": os.Getenv("ISPHERE_PACKET_LOG_FILE") != "",
|
||||||
"real_isphere_login_required": false,
|
"real_isphere_login_required": false,
|
||||||
"python_runtime_required": false,
|
"python_runtime_required": false,
|
||||||
"action_tools_present": false,
|
"action_tools_present": false,
|
||||||
@@ -229,6 +237,7 @@ func fail(step string, err error) {
|
|||||||
tool_count = $script:harnessJson.tool_count
|
tool_count = $script:harnessJson.tool_count
|
||||||
tools = $script:harnessJson.tools
|
tools = $script:harnessJson.tools
|
||||||
receive_message_count = $script:harnessJson.receive_message_count
|
receive_message_count = $script:harnessJson.receive_message_count
|
||||||
|
packet_log_file_configured = $script:harnessJson.packet_log_file_configured
|
||||||
python_runtime_required = $false
|
python_runtime_required = $false
|
||||||
real_isphere_login_required = $false
|
real_isphere_login_required = $false
|
||||||
action_tools_present = $false
|
action_tools_present = $false
|
||||||
|
|||||||
Reference in New Issue
Block a user