Align `dws event consume` with an AI-subprocess contract an orchestrator
can drive deterministically, and expose a machine-readable input schema
for event commands via `dws schema`.
Subprocess contract:
- Fixed stderr ready line `[event] ready event_key=<key> bus_pid=<pid>`;
block on it instead of sleeping.
- Final `[event] exited — received N event(s) in Xs (reason: ...)` line;
exit 0 on controlled exit, non-zero and no exited line on failure.
- stdin-EOF graceful shutdown, armed only for a pipe stdin on an
unbounded run; an interactive TTY and `< /dev/null` never trigger it.
- Ownership-based subscription cleanup: a run-created subscription is
unsubscribed on any clean exit while a --subscribe-id-reused one is
kept (--ephemeral still forces cleanup). Forward --profile to the
detached bus so non-default orgs resolve the right credentials, and
surface the child's real startup error over the ready pipe.
Schema:
- `dws schema "event consume"` (or event.consume) synthesizes a flat,
machine-readable schema from the command's cobra flags:
{description, path, source:"cobra",
parameters{<flag>:{type,required,description,default?}}} plus an
`arguments` array for positional inputs. Intermediate nodes list
subcommands. Inherited global flags and hidden internal flags are
excluded so the schema describes just that command.
- Reusable registry (cobraSchemaRoots); event is the first consumer and
more command trees can opt in without further wiring.
Docs: mono + dingtalk-event skills document the contract and the two
schema surfaces; design notes in docs/event-subprocess-contract.md.
65 lines
2.1 KiB
Go
65 lines
2.1 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package app
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/personal"
|
|
)
|
|
|
|
// A bounded run never arms the stdin-EOF watcher, regardless of stdin
|
|
// shape: --max-events / --duration are the lifecycle control.
|
|
func TestShouldWatchStdinEOF_BoundedIsNeverArmed(t *testing.T) {
|
|
if shouldWatchStdinEOF(1, 0) {
|
|
t.Error("--max-events set should not arm stdin watcher")
|
|
}
|
|
if shouldWatchStdinEOF(0, 5*time.Second) {
|
|
t.Error("--duration set should not arm stdin watcher")
|
|
}
|
|
if shouldWatchStdinEOF(3, 2*time.Second) {
|
|
t.Error("both bounds set should not arm stdin watcher")
|
|
}
|
|
}
|
|
|
|
// Regression: the detached _bus child must receive --profile so it resolves
|
|
// credentials for the same organization as the parent. Missing it made a
|
|
// non-default `--profile` consume fail with "bus child reported startup
|
|
// failure on ready pipe" (no bus.log).
|
|
func TestPersonalBusSpawnArgs_ForwardsProfile(t *testing.T) {
|
|
args := personalBusSpawnArgs(personal.Identity{
|
|
CorpID: "dinga626d60c1128d449",
|
|
SourceID: "open",
|
|
}, "", "")
|
|
found := false
|
|
for i := 0; i+1 < len(args); i++ {
|
|
if args[i] == "--profile" && args[i+1] == "dinga626d60c1128d449" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("spawn args must forward --profile <corpId>; got %v", args)
|
|
}
|
|
|
|
// No CorpID → no --profile appended (avoid an empty flag value).
|
|
bare := personalBusSpawnArgs(personal.Identity{SourceID: "open"}, "", "")
|
|
for _, a := range bare {
|
|
if a == "--profile" {
|
|
t.Errorf("must not append --profile when CorpID is empty; got %v", bare)
|
|
}
|
|
}
|
|
}
|