Skip to content

feat(hints)!: give every error one hint for the SDK and one for the CLI - #698

Merged
karhu merged 1 commit into
tnet-157/20-store-toolsfrom
tnet-157/21-hints
Nov 10, 2026
Merged

karhu merged 1 commit into
tnet-157/20-store-toolsfrom
tnet-157/21-hints

Conversation

@karhu

@karhu karhu commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

The Stack

This stack covers refactoring the internal logic of how releases are resolved during read, download & build, in particular also in the context of parent releases / parent datasets. On top of that it refactors the public TimeNet APIs and CLI interfaces around these actions, to streamline the behavior across them.

This PR

Every TimeNet error gets one hint: the call or the command that gets the user out of the situation. The SDK names Python calls, and both CLIs name commands and flags.

Details

  • An error message states facts only. TimeNetError has a hint, and str() puts it after the message.
  • The new private module timenet._hints picks the hint from the facts of the call: the sources, the store, the request and the flags of a command. The client, timenet.build(), plan_build() and publish() use the SDK wording. timenet and timenet-build use the CLI wording.
  • A hint names only a call or a command that works in that situation. So it looks at the registries, the installed connectors and the versions that they provide:
    • A release that no source provides names a registry to add. If a connector builds the release, it also names the installed connectors. If no connector package is installed, it names one.
    • A damaged copy in the store names a download from a registry, else a forced build, else a registry to add. A failed transfer gets no hint.
    • A download of the latest version that only a connector provides names the build and the exact version that a registry holds.
    • A version conflict with the store names the copy to delete. A conflict on publish names a new version.
    • A registry that cannot be reached, over HTTP or S3, names the version in the store.
    • A stored manifest that is not valid names the folder to delete.
    • An isolated build that cannot make its environment names --no-isolation or TIMENET_ISOLATION=off.
  • TimeNetManifestError and TimeNetIsolationError mark the last two situations. They subclass TimeFFormatError and TimeNetBuildError.
  • A registry flag that names no registry (--local-registry, --remote-registry, --publish-to, --dest, --source) is a usage error. It exits with code 2 and names the flag. A bad TIMENET_* variable still exits with code 1 and names the variable.
  • timenet-build build --publish-to compares the release in the store with the release in the destination. For another release, it raises TimeNetVersionConflictError before the build. If the store lacks the release and the destination holds the version, the command prints "Nothing to do" and builds nothing. With --force, it builds again, and the publish compares the files.
  • The root help of timenet says what the tool does, and timenet-build build --help no longer shows a Raises section.
  • Size: about 1880 changed lines: about 1260 in the source and 620 in tests. About 690 are deletions, because the hint helpers of the client, the build CLI and the consumer CLI move into one module.

Design decisions

  • The hint is separate from the message. So the SDK and the CLI share one error and differ only in the advice, and a caller can read the facts without the advice.
  • One module writes every hint, in both wordings. Before, three places wrote their own, and the CLI showed SDK advice.
  • A hint never suggests a command that fails. For example, a damaged manifest gets "delete the folder", because a download or a build reads that manifest first and fails.
  • --publish-to without --force trusts a destination that holds the version. A rebuild only to compare costs a full build, so --force makes that check explicit.
  • The two new error classes let the hint layer tell these situations apart without parsing messages.

Breaking changes

  • Error messages no longer contain remedies. The remedy is in error.hint, which str() shows after the message.
  • A registry flag that names no registry exits with code 2, not 1.
  • If the store holds another release than the destination, timenet-build build --publish-to raises TimeNetVersionConflictError.
  • The message for an empty setting no longer names a flag.

Stack

Changes later in the stack:

Merge message

feat(hints)!: give every error one hint for the SDK and one for the CLI

Every TimeNet error gets one hint: the call or the command that gets the
user out of the situation. The message states only facts, and str()
shows the hint after it.

- timenet._hints picks the hint from the facts of the call: the sources,
  the local registry, the request and the flags. The SDK names Python
  calls, and both CLIs name commands and flags.
- A hint names only a call or a command that works in that situation.
- TimeNetUnreachableError, TimeNetManifestError, TimeNetOriginError and
  TimeNetIsolationError mark an unreachable registry, an invalid stored
  manifest, an invalid origin record and an isolated build that cannot
  make its environment.
- timenet-build build --publish-to refuses another release in the
  destination before the build.

BREAKING CHANGE: Error messages no longer contain remedies. The remedy
is in error.hint.

BREAKING CHANGE: A registry flag that names no registry is a usage error
and exits with code 2, not 1.

@karhu
karhu added this pull request to stack #699 November 6, 2026 08:10
This was referenced Nov 6, 2026
@karhu
karhu force-pushed the tnet-157/21-hints branch from e2e63c8 to f9d1c80 Compare November 6, 2026 10:35
@karhu
karhu force-pushed the tnet-157/21-hints branch from f9d1c80 to 8febc08 Compare November 6, 2026 10:57
@karhu
karhu marked this pull request as ready for review November 6, 2026 13:11
@karhu
karhu force-pushed the tnet-157/21-hints branch from e357bc3 to 1fdb781 Compare November 9, 2026 11:11

@TimoStoff TimoStoff left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Comment thread packages/timenet/src/timenet/_hints.py Resolved
Comment thread packages/timenet/src/timenet/_hints.py Resolved
Comment thread packages/timenet/src/timenet/client.py Resolved
Comment thread packages/timenet/src/timenet/_hints.py Resolved
Comment thread packages/timenet/src/timenet/_hints.py Resolved
Comment thread packages/timenet/tests/test_cli.py Resolved
Comment thread packages/timenet/tests/test_client.py Resolved
@karhu
karhu force-pushed the tnet-157/21-hints branch from 1fdb781 to 8f39064 Compare November 9, 2026 12:23
@karhu
karhu force-pushed the tnet-157/21-hints branch from 8f39064 to cfd2fbf Compare November 9, 2026 13:56
@karhu
karhu force-pushed the tnet-157/21-hints branch from cfd2fbf to 7d4c7b7 Compare November 9, 2026 15:06
@karhu
karhu force-pushed the tnet-157/21-hints branch 2 times, most recently from 4917142 to 518ce17 Compare November 9, 2026 16:50
@karhu
karhu force-pushed the tnet-157/21-hints branch from 518ce17 to df62a91 Compare November 9, 2026 18:57
@karhu
karhu force-pushed the tnet-157/21-hints branch from df62a91 to eb6db0e Compare November 9, 2026 19:16
@karhu
karhu force-pushed the tnet-157/21-hints branch from eb6db0e to c83629d Compare November 10, 2026 14:39

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread packages/timenet/src/timenet/_hints.py Resolved
@karhu
karhu force-pushed the tnet-157/21-hints branch from c83629d to 184c7d5 Compare November 10, 2026 15:50
@karhu
karhu removed this pull request from stack #699 November 10, 2026 15:58

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

@karhu
karhu force-pushed the tnet-157/21-hints branch from 184c7d5 to 9f8fb46 Compare November 10, 2026 16:24
@karhu
karhu added this pull request to stack #772 November 10, 2026 16:24

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped: your organization has reached its API usage limit, so Code Review can't run right now.

An organization admin can review the limit in your organization's usage and billing settings.

Once the limit resets or is raised, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

The remedy of an error lived in many places. Some messages carried
it, and the client and timenet-build each had their own hint helpers.
The timenet CLI showed the advice of the SDK, such as "add
InstalledConnectors()". Some hints named a command that fails in that
situation. download(), timenet.build() and timenet.publish() gave no
hint for a missing parent.

An error message now states facts only. TimeNetError has a hint, and
str() puts it after the message. The new module timenet._hints picks the
hint from the facts of the call: the sources, the local registry and the
request. A HintWording gives the parts of the hint that name calls or
commands. A context variable holds the current wording. Its default is
SdkWording, which names SDK calls. Each command of both CLIs sets
CliWording with use_wording(), and CliWording names commands with the
registry flags that the command got. So the client, timenet.build() and
publish() compute each hint once, in the words of the front end. A
command that calls no SDK function, such as timenet local clear, adds
the hint itself. The hint code picks the remedy by the class of the
error, and a subclass without a remedy of its own gets the remedy of its
nearest base class. A lookup that fails with a registry, format or card
error gives no hint. Any other error of the lookup surfaces, so a bug in
it does not hide. A hint names only a call or a command that works. So
it looks at the local registry, the installed connectors and the
registries that the call already has, and at the versions that they
provide. It opens no other registry:

- A release that no source provides names a registry to add. It names
  the installed connectors only if a connector builds the release, and a
  connector package if none is installed.
- A publish of a release that the local registry lacks names the build
  if an installed connector builds the release, else the download. It
  asks no registry, so it does not check that a registry holds the
  release.
- A damaged copy in the local registry names a download from a registry,
  else a forced build, else a registry to add. A failed transfer gets no
  hint.
- A download that only a connector can serve, for example of "latest"
  when a connector declares a newer version than any registry, names the
  build and the exact version that a registry holds.
- A version conflict with the local registry names the copy to delete. A
  conflict on publish names a new version. A conflict that
  timenet-build build --publish-to finds before the build names --force,
  which compares a fresh build with the published release.
- A read or a download of "latest" that cannot reach a registry names
  the version in the local registry.
- A stored manifest that is not valid names the folder to delete,
  because a download or a build reads that manifest first.
- A provenance record in the local registry that is not valid names the
  forced download and the forced build. Both replace the release and its
  record. The info and the clear commands of timenet local give this
  hint.
- An isolated build that cannot make its environment names
  --no-isolation or TIMENET_ISOLATION=off.
- A list or a search whose sources have no catalog names the read of
  an exact release: open('<id>@<version>') in the SDK and timenet info
  <id>@<version> in the CLI.

Four errors mark some of these situations. TimeNetUnreachableError, a
subclass of TimeNetRegistryError, marks a registry that a request cannot
reach. TimeNetManifestError marks a stored manifest that is not valid,
and its path names the file. TimeNetOriginError marks a provenance
record that is not valid. Both are subclasses of TimeFFormatError.
TimeNetIsolationError, a subclass of TimeNetBuildError, marks an
environment that an isolated build cannot make.
LocalRegistry.release_at() finds the release whose folder holds a path.

TimeNetVersionConflictError gives the location of the destination
registry in registry_location, so a hint can compare it with the local
registry. Its difference is a ReleaseDifference, new in timenet.errors:
FILES or CARD. The difference is now a required argument, and it comes
before needed_by. Manifest.difference_from() compares two manifests of
a release and gives this value. same_release() is gone.

A registry flag that names no registry (--local-registry,
--remote-registry, --publish-to, --dest, --source) is now a usage
error. It exits with code 2 and names the flag. A bad TIMENET_*
variable still exits with code 1 and names the variable.

timenet-build build --publish-to now compares the release in the local
registry with the release in the destination. For another release, it
raises TimeNetVersionConflictError before the build. If the local
registry lacks the release, the version in the destination is enough,
and the command says "Nothing to do". With --force, the command builds
the release again, and the publish compares the files.

The root help of timenet says what the tool does. The help of
timenet-build build no longer shows its Raises section. Every command
has the same help for --local-registry: the local registry, where
downloads and builds land and which a lookup checks first.

BREAKING CHANGE: error messages no longer contain remedies. The remedy
is in error.hint, which str() shows after the message.

Part of TNET-157.
@karhu
karhu force-pushed the tnet-157/21-hints branch from 9f8fb46 to 623c93d Compare November 10, 2026 17:41
@karhu
karhu merged commit 5566df9 into main Nov 10, 2026
4 of 8 checks passed
@karhu
karhu deleted the tnet-157/21-hints branch November 10, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants