Open a TaskStream board when a project is created through the API
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
Tests / test-postgres (push) Has been cancelled
Tests / mcp (push) Has been cancelled

`taskstream: true` on POST /v1/projects also creates the project in
TaskStream, public, as the caller (their Pireon token is passed on), and
the answer carries `taskstream_key` and `taskstream_url`. The key is
`taskstream_key` or one made from the default-language slug.

The board is opened before the project is saved: if TaskStream refuses
it, nothing is created here and the reason comes back as a validation
error (502 when TaskStream does not answer). Machine tokens need the
server's own TASKSTREAM_TOKEN. Updates ignore the flag, and duplicates do
not inherit the link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 21:12:36 +02:00
parent 9390db18c2
commit d30dd971d7
15 changed files with 621 additions and 10 deletions

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Services\TaskStream;
use RuntimeException;
use Throwable;
class TaskStreamException extends RuntimeException
{
public function __construct(
string $message,
public readonly ?int $status = null,
?Throwable $previous = null,
) {
parent::__construct($message, $status ?? 0, $previous);
}
/**
* TaskStream refused what it was asked for (a key in use, a bad name):
* the caller can fix it and try again.
*/
public function isRefusal(): bool
{
return in_array($this->status, [400, 409, 422], true);
}
/**
* The token was not good enough there: expired, or without access.
*/
public function isUnauthorised(): bool
{
return in_array($this->status, [401, 403], true);
}
public static function unreachable(string $url, ?Throwable $previous = null): self
{
return new self("TaskStream could not be reached at [{$url}].", previous: $previous);
}
}