Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions docs/integrations/lark-kanban-control-plane-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ The recommended path is `setup`, using user identity by default. It preflights
python3 -m loopx.cli lark-kanban doctor
lark-cli auth login --domain base --recommend
python3 -m loopx.cli lark-kanban setup --base-name "LoopX Kanban POC" --execute
python3 -m loopx.cli lark-kanban sync-loopx-todos --goal-id <goal-id> --execute
python3 -m loopx.cli lark-kanban sync-loopx-todos --goal-id <goal-id> --sink-visibility shared --execute
```

The local config stores the reusable Base token, table id, view ids, identity,
Expand All @@ -206,7 +206,7 @@ To use someone else's shared board, store its URL or IDs:
```bash
python3 -m loopx.cli lark-kanban use --base-url "<shared-base-url>"
python3 -m loopx.cli lark-kanban config
python3 -m loopx.cli lark-kanban heartbeat --execute-lark
python3 -m loopx.cli lark-kanban heartbeat --execute-lark --sink-visibility shared
```

`sync-loopx-todos` reads the goal active state from the LoopX registry and
Expand All @@ -218,6 +218,29 @@ cards; claimed agent todos become `Claimed`; blocked/done todos map to
as a worker-launch row; the shared board must not receive raw local checkout or
active-state paths.

`sync-loopx-todos` and `heartbeat` default to `--sink-visibility shared`. Every
record writer removes canonical provider-credential shapes before serialization,
including on an `owner-only` board. Shared visibility additionally applies the
local-path, private-link, and private-reference redaction boundary recursively to
nested record keys and values while preserving only structurally valid
record-link IDs. It recognizes Unix, drive-letter, UNC, and extended Windows
paths, plus credential assignments that use compound environment-variable names,
credential-bearing URL userinfo, and terminal control sequences around a token.
Configured Explore whiteboards apply the same visibility boundary to node and
edge display text before SVG or Mermaid is encoded, and shared Explore stage
documents sanitize lane labels before their `docs +update` content is built.
Redaction never runs over raw markup, so encoded tags and diagram delimiters
stay intact even when a value ends with a private link. Private-key blocks are
removed in full rather than leaving their body behind. The automatic issue-fix
Explore synchronization uses the same shared boundary.

`sink_visibility` is a caller declaration; the adapter cannot verify the remote
Base's effective access control. Redaction is shape-based, not a complete
data-loss-prevention system: it does not classify every arbitrary high-entropy
value without a provider prefix or credential label. Never paste credentials
into board fields, and rotate any credential that may already have reached shared
storage rather than relying on later clean-up.

Issue outcomes are derived, not persisted separately. Every feasibility row is
projected; a PR lifecycle row enriches it only through an explicit matching
`repo` and `issue_ref`. This avoids title/branch guessing and makes the issue
Expand Down Expand Up @@ -261,20 +284,25 @@ python3 -m loopx.cli lark-kanban doctor
python3 -m loopx.cli lark-kanban setup --base-name "LoopX Kanban POC" --execute
python3 -m loopx.cli lark-kanban use --base-url "<shared-base-url>"
python3 -m loopx.cli lark-kanban config
python3 -m loopx.cli lark-kanban sync-loopx-todos --goal-id <goal-id> --execute
python3 -m loopx.cli lark-kanban sync-loopx-todos --goal-id <goal-id> --sink-visibility shared --execute
python3 -m loopx.cli lark-kanban sync-projection --projection-file <complete.json> --include-done --reconcile-source --source-snapshot-complete
python3 -m loopx.cli lark-kanban plan-create --base-name "LoopX Kanban POC"
python3 -m loopx.cli lark-kanban create-board --base-name "LoopX Kanban POC" --execute
python3 -m loopx.cli lark-kanban seed-task --base-token <base> --table-id <table> --execute
python3 -m loopx.cli lark-kanban seed-cases --base-token <base> --table-id <table> --execute
python3 -m loopx.cli lark-kanban heartbeat --base-token <base> --table-id <table> --execute-lark
python3 -m loopx.cli lark-kanban seed-task --base-token <base> --table-id <table> --sink-visibility shared --execute
python3 -m loopx.cli lark-kanban seed-cases --base-token <base> --table-id <table> --sink-visibility shared --execute
python3 -m loopx.cli lark-kanban heartbeat --base-token <base> --table-id <table> --execute-lark --sink-visibility shared
```

`setup`, `create-board`, `seed-task`, `seed-cases`, `sync-loopx-todos`, and
`heartbeat` are dry-run unless their explicit execute flags are set. Worker
execution has its own gate, `--execute-worker`, and an allowlist gate,
`--allow-command-prefix`.

`seed-task` and `seed-cases` default to `--sink-visibility shared`, like the
operator sync and heartbeat paths. They apply the same recursive credential and
local-path boundary before the first row is written; pass `--sink-visibility
owner-only` only when the target board's ACL is independently verified.

`seed-cases` creates one UX optimization task plus four feasibility cases:

- a `notes.zaynjarvis.com` LoopX architecture/decision-note lane;
Expand All @@ -287,10 +315,14 @@ execution has its own gate, `--execute-worker`, and an allowlist gate,
The prototype deliberately keeps raw agent transcripts, credentials, local
private paths, and hidden benchmark material out of Lark rows. `Evidence` and
`Run History` should contain compact public-safe summaries or artifact
pointers. `sync-loopx-todos` must not populate `Workdir` from the local active
state path. If a real worker needs to store detailed logs or local launch
context, store them in the worker's normal trace surface and write only a
compact pointer back to Base.
pointers. Every record writer, including `seed-task`, `seed-cases`,
`sync-loopx-todos`, projection sync, and heartbeat writeback, applies
credential-shape redaction before serialization; shared visibility additionally
redacts local paths and private references from nested keys and values.
`sync-loopx-todos` must not populate `Workdir` from the local active state path.
If a real worker needs to store detailed logs or local launch context, store
them in the worker's normal trace surface and write only a compact pointer back
to Base.

## Validation

Expand Down
8 changes: 4 additions & 4 deletions loopx/canary/module_metric_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@
"dict_any_count": 0
},
"loopx/extensions/lark/presentation/explore_results.py": {
"any_count": 68,
"any_count": 78,
"dict_any_count": 0,
"lines": 2276
"lines": 2458
},
"loopx/extensions/lark/presentation/kanban.py": {
"any_count": 95,
"any_count": 100,
"dict_any_count": 0,
"lines": 2791
"lines": 2760
},
"loopx/heartbeat_prompt.py": {
"any_count": 17,
Expand Down
2 changes: 2 additions & 0 deletions loopx/cli_commands/explore_feishu_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ def handle_explore_feishu_command(
projection=visual_projection,
visual_sinks=visual_sinks,
config_path=config_path,
sink_visibility=args.sink_visibility,
execute=bool(args.execute),
)
else:
Expand All @@ -285,6 +286,7 @@ def handle_explore_feishu_command(
else None,
config_path=config_path,
semantic_digest=explore_visual_semantic_digest(projection),
sink_visibility=args.sink_visibility,
execute=bool(args.execute),
)
payload["ok"] = bool(payload.get("ok")) and bool(
Expand Down
35 changes: 34 additions & 1 deletion loopx/cli_commands/lark_kanban.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
from ..extensions.lark.presentation.explore_results import (
sync_issue_fix_explore_on_material_change,
)
from ..extensions.lark.presentation.sink_visibility import SINK_VISIBILITY_OWNER_ONLY, SINK_VISIBILITIES
from ..extensions.lark.presentation.sink_visibility import (
SINK_VISIBILITY_OWNER_ONLY,
SINK_VISIBILITY_SHARED,
SINK_VISIBILITIES,
)
from ..extensions.runtime import (
default_extension_state_file,
resolve_extension_activation,
Expand Down Expand Up @@ -137,6 +141,12 @@ def register_lark_kanban_commands(
seed.add_argument("--goal-id", default="loopx-lark-kanban-poc")
seed.add_argument("--worker-command", default="")
seed.add_argument("--workdir", default="")
seed.add_argument(
"--sink-visibility",
choices=sorted(SINK_VISIBILITIES),
default=SINK_VISIBILITY_SHARED,
help="Shared boards receive public-safe redaction; defaults to shared for this operator sync.",
)
seed.add_argument("--execute", action="store_true", help="Actually upsert the sample record.")

cases = sub.add_parser(
Expand All @@ -149,6 +159,12 @@ def register_lark_kanban_commands(
cases.add_argument("--goal-id", default="loopx-lark-kanban-ux")
cases.add_argument("--worker-command", default="")
cases.add_argument("--workdir", default="")
cases.add_argument(
"--sink-visibility",
choices=sorted(SINK_VISIBILITIES),
default=SINK_VISIBILITY_SHARED,
help="Shared boards receive public-safe redaction; defaults to shared for this operator sync.",
)
cases.add_argument("--execute", action="store_true", help="Actually upsert the records.")

sync = sub.add_parser(
Expand All @@ -166,6 +182,12 @@ def register_lark_kanban_commands(
sync.add_argument("--project")
sync.add_argument("--state-file")
sync.add_argument("--include-done", action="store_true")
sync.add_argument(
"--sink-visibility",
choices=sorted(SINK_VISIBILITIES),
default=SINK_VISIBILITY_SHARED,
help="Shared boards receive public-safe redaction; defaults to shared for this operator sync.",
)
sync.add_argument(
"--limit",
type=int,
Expand Down Expand Up @@ -254,6 +276,12 @@ def register_lark_kanban_commands(
"--worker-command",
help="Override the row's Worker Command for this heartbeat.",
)
heartbeat.add_argument(
"--sink-visibility",
choices=sorted(SINK_VISIBILITIES),
default=SINK_VISIBILITY_SHARED,
help="Shared boards receive public-safe redaction; defaults to shared for this operator board.",
)
heartbeat.add_argument(
"--allow-command-prefix",
action="append",
Expand Down Expand Up @@ -441,6 +469,7 @@ def handle_lark_kanban_command(
payload = seed_lark_kanban_task(
_target_config(args, config_path=config_path),
task=task,
sink_visibility=args.sink_visibility,
execute=bool(args.execute),
)
payload["execute"] = bool(args.execute)
Expand All @@ -457,6 +486,7 @@ def handle_lark_kanban_command(
payload = seed_lark_kanban_records(
_target_config(args, config_path=config_path),
records=records,
sink_visibility=args.sink_visibility,
execute=bool(args.execute),
)
payload["execute"] = bool(args.execute)
Expand All @@ -473,6 +503,7 @@ def handle_lark_kanban_command(
state_file=Path(args.state_file).expanduser() if args.state_file else None,
include_done=bool(args.include_done),
limit=args.limit,
sink_visibility=args.sink_visibility,
execute=bool(args.execute),
)
try:
Expand All @@ -484,6 +515,7 @@ def handle_lark_kanban_command(
agent_id=args.agent_id,
project=Path(args.project).expanduser() if args.project else None,
state_file=Path(args.state_file).expanduser() if args.state_file else None,
sink_visibility=args.sink_visibility,
execute=bool(args.execute),
)
projection_result = (
Expand Down Expand Up @@ -588,6 +620,7 @@ def handle_lark_kanban_command(
agent_id=args.agent_id,
fixture=fixture,
worker_command=args.worker_command,
sink_visibility=args.sink_visibility,
execute_lark=bool(args.execute_lark),
execute_worker=bool(args.execute_worker),
complete_on_success=bool(args.complete_on_success),
Expand Down
2 changes: 1 addition & 1 deletion loopx/control_plane/goals/vision_checkpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const LABELED_CREDENTIAL_ASSIGNMENT = /\b(?:Bearer|token|password|secret)["']?\s
// Python detector -- a raw remote location -- is deliberately not ported: it is
// outside the internal-state policy, and the per-face URL decision is the
// caller-migration work still open in #5136.
const SECRET_LIKE_SHAPE = new RegExp("(?:\\bbearer\\s+[a-z0-9._~+/=-]{16,}|\\b(?:access|api|secret)[_-]?key[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]+|\\b(?:ak|sk)[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]+|(?<![a-z0-9_])(?:ak|sk)[-_=:][a-z0-9_=-]{10,}|\\bgh[pousr]_[a-z0-9]{16,}\\b|\\bgithub_pat_[a-z0-9_]{20,}|\\b(?:akia|asia)[a-z0-9]{16}\\b|\\bxox[baprs]-[a-z0-9-]{10,}|\\baiza[a-z0-9_-]{20,}|\\b(?:sk|rk)_(?:live|test)_[a-z0-9]{12,}|\\bnpm_[a-z0-9]{20,}|\\bpypi-[a-z0-9_-]{20,}|\\beyj[a-z0-9_-]{10,}\\.[a-z0-9_-]{10,}\\.[a-z0-9_-]{10,}\\b|\\b(?:access|refresh)[_-]?token[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]{12,}|\\b(?:password|secret)[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]{12,}|-{3,}\\s*BEGIN (?:[A-Z]+ )?PRIVATE KEY|\\btoken[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]{12,})", "i");
const SECRET_LIKE_SHAPE = new RegExp("(?:\\bbearer\\s+[a-z0-9._~+/=-]{16,}|\\b[a-z][a-z0-9+.-]*://[^/\\s:@`'\\\"<>]+:[^/\\s@`'\\\"<>]+@[^\\s`'\\\"<>]*|\\b(?:(?:aws[_-]?)?secret[_-]?access[_-]?key|(?:access|api|secret)[_-]?key)[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]+|\\b(?:ak|sk)[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]+|(?<![a-z0-9_])(?:ak|sk)[-_=:][a-z0-9_=-]{10,}|\\bgh[pousr]_[a-z0-9]{16,}\\b|\\bgithub_pat_[a-z0-9_]{20,}|\\b(?:akia|asia)[a-z0-9]{16}\\b|\\bxox[baprs]-[a-z0-9-]{10,}|\\baiza[a-z0-9_-]{20,}|\\b(?:sk|rk)_(?:live|test)_[a-z0-9]{12,}|\\bsk-(?:proj|svcacct|admin)-[a-z0-9_-]{20,}|\\bsk-ant-[a-z0-9_-]{20,}|\\bnpm_[a-z0-9]{20,}|\\bpypi-[a-z0-9_-]{20,}|\\beyj[a-z0-9_-]{10,}\\.[a-z0-9_-]{10,}\\.[a-z0-9_-]{10,}\\b|\\b(?:access|refresh)[_-]?token[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]{12,}|\\b(?:password|secret)[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]{12,}|-{3,}\\s*BEGIN (?:[A-Z]+ )?PRIVATE KEY[\\s\\S]*?(?:-{3,}\\s*END (?:[A-Z]+ )?PRIVATE KEY-{3,}|$)|\\btoken[\\\"']?\\s*[=:]\\s*[\\\"']?[^\\s`'\\\"<>]{12,})", "i");
const LOCAL_PATH_SHAPE = new RegExp("(?<![:/A-Za-z0-9])(?:/(?:Users|home|Volumes|private|tmp|var|etc|opt|srv|mnt|root|data|workspace|workspaces)/[^\\s`'\\\"<>]+|[A-Za-z]:[\\\\/][^\\s`'\\\"<>]+|\\\\\\\\[A-Za-z0-9_.-]+\\\\[^\\s`'\\\"<>]+)", "i");
export const CREDENTIAL_WORD_PATTERNS: RegExp[] = [/\bBearer\b/i, /\bpassword\b/i, /\bsecret\b/i];
export const PRIVATE_TEXT_PATTERNS: RegExp[] = [
Expand Down
Loading
Loading